diff --git a/src/Reflection/InitializerExprTypeResolver.php b/src/Reflection/InitializerExprTypeResolver.php index abf5384f2a4..097176485d0 100644 --- a/src/Reflection/InitializerExprTypeResolver.php +++ b/src/Reflection/InitializerExprTypeResolver.php @@ -2335,8 +2335,13 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri $leftNumberType = $leftType->toNumber(); $rightNumberType = $rightType->toNumber(); + // a template type of mixed does not absorb the other operand in the union + $typesMixed = $types instanceof MixedType + || $leftType instanceof MixedType + || $rightType instanceof MixedType; + if ( - !$types instanceof MixedType + !$typesMixed && ( $rightNumberType instanceof IntegerRangeType || $rightNumberType instanceof ConstantIntegerType @@ -2397,14 +2402,14 @@ private function resolveCommonMath(Expr\BinaryOp $expr, Type $leftType, Type $ri $resultType = TypeCombinator::union($leftNumberType, $rightNumberType); if ($expr instanceof Expr\BinaryOp\Div) { - if ($types instanceof MixedType || $resultType->isInteger()->yes()) { + if ($typesMixed || $resultType->isInteger()->yes()) { return new BenevolentUnionType([new IntegerType(), new FloatType()]); } return new UnionType([new IntegerType(), new FloatType()]); } - if ($types instanceof MixedType + if ($typesMixed || $leftType instanceof BenevolentUnionType || $rightType instanceof BenevolentUnionType ) { diff --git a/tests/PHPStan/Analyser/nsrt/bug-15388.php b/tests/PHPStan/Analyser/nsrt/bug-15388.php new file mode 100644 index 00000000000..3187e30115a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-15388.php @@ -0,0 +1,53 @@ + $vals */ +function foo(array $vals): void { + assertType('(float|int)', array_reduce($vals, function($carry, $val) { + return $carry + map($val); + }, 0)); + assertType('(float|int)', array_reduce($vals, function($carry, $val) { + return $carry + $val; + }, 0)); + assertType('(float|int)', array_reduce($vals, function($carry, int $val) { + return $carry - $val; + }, 0)); + assertType('(float|int)', array_reduce($vals, function($carry, int $val) { + return $carry * $val; + }, 1)); +} + +function map(int $val): int { + return $val; +} + +/** + * @template T + * @param T $t + * @param mixed $m + */ +function templateOfMixed($t, $m, int $i): void +{ + assertType('(float|int)', $m + $i); + assertType('(float|int)', $t + $i); + assertType('(float|int)', $i + $t); + assertType('(float|int)', $t + 1); + assertType('(float|int)', $t - $i); + assertType('(float|int)', $t - 1); + assertType('(float|int)', $t * $i); + assertType('(float|int)', $t / $i); + assertType('(float|int)', $t ** $i); + assertType('(float|int)', $t << $i); + assertType('int', $t % $i); + + $a = $t; + $a += $i; + assertType('(float|int)', $a); + + $a = $t; + $a++; + assertType('(float|int)', $a); +} diff --git a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php index daccbe3882c..bdd01c80cb2 100644 --- a/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php +++ b/tests/PHPStan/Rules/Functions/ReturnTypeRuleTest.php @@ -542,6 +542,13 @@ public function testBug11507(): void $this->analyse([__DIR__ . '/data/bug-11507.php'], []); } + public function testBug15388(): void + { + $this->checkNullables = true; + $this->checkExplicitMixed = false; + $this->analyse([__DIR__ . '/data/bug-15388.php'], []); + } + public function testBug15245(): void { $this->checkNullables = true; diff --git a/tests/PHPStan/Rules/Functions/data/bug-15388.php b/tests/PHPStan/Rules/Functions/data/bug-15388.php new file mode 100644 index 00000000000..ff9841e83bc --- /dev/null +++ b/tests/PHPStan/Rules/Functions/data/bug-15388.php @@ -0,0 +1,21 @@ + $vals */ +function foo(array $vals): int { + return array_reduce($vals, function($carry, $val) { + return $carry + map($val); + }, 0); +} + +/** @param list $vals */ +function bar(array $vals): int { + return array_reduce($vals, function($carry, $val) { + return $carry + $val; + }, 0); +} + +function map(int $val): int { + return $val; +} diff --git a/turbo-ext/src/InitializerExprTypeResolver.cpp b/turbo-ext/src/InitializerExprTypeResolver.cpp index a8dccb753ac..cb1e968af0e 100644 --- a/turbo-ext/src/InitializerExprTypeResolver.cpp +++ b/turbo-ext/src/InitializerExprTypeResolver.cpp @@ -2678,7 +2678,10 @@ class InitializerExprTypeResolver IETR_VAL(leftNumberType, callOn(leftType, mToNumber)); IETR_VAL(rightNumberType, callOn(rightType, mToNumber)); - bool typesMixed = isA(types.raw(), pt_ce_mixed_type); + // a template type of mixed does not absorb the other operand in the union + bool typesMixed = isA(types.raw(), pt_ce_mixed_type) + || isA(leftType, pt_ce_mixed_type) + || isA(rightType, pt_ce_mixed_type); if ( !typesMixed && (