Skip to content

Commit e61bf3e

Browse files
VincentLangletphpstan-bot
authored andcommitted
Fix conditional parameter type narrowing for union types with 3+ members
- Split union condition types into individual ConditionalExpressionHolders - When TypeCombinator::intersect or ::remove produces a UnionType, each member gets its own holder so the equals() check can match individual constant types - New regression test in tests/PHPStan/Analyser/nsrt/bug-10055.php
1 parent 9c915ec commit e61bf3e

2 files changed

Lines changed: 38 additions & 9 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,15 +1758,24 @@ private function enterFunctionLike(
17581758
$ifType = $parameterType->isNegated() ? $parameterType->getElse() : $parameterType->getIf();
17591759
$elseType = $parameterType->isNegated() ? $parameterType->getIf() : $parameterType->getElse();
17601760

1761-
$holder = new ConditionalExpressionHolder([
1762-
$parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), TypeCombinator::intersect($targetParameter->getType(), $parameterType->getTarget())),
1763-
], ExpressionTypeHolder::createYes(new Variable($parameter->getName()), $ifType));
1764-
$conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
1765-
1766-
$holder = new ConditionalExpressionHolder([
1767-
$parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), TypeCombinator::remove($targetParameter->getType(), $parameterType->getTarget())),
1768-
], ExpressionTypeHolder::createYes(new Variable($parameter->getName()), $elseType));
1769-
$conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
1761+
$ifConditionType = TypeCombinator::intersect($targetParameter->getType(), $parameterType->getTarget());
1762+
$elseConditionType = TypeCombinator::remove($targetParameter->getType(), $parameterType->getTarget());
1763+
1764+
$ifConditionTypes = $ifConditionType instanceof UnionType ? $ifConditionType->getTypes() : [$ifConditionType];
1765+
foreach ($ifConditionTypes as $conditionType) {
1766+
$holder = new ConditionalExpressionHolder([
1767+
$parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), $conditionType),
1768+
], ExpressionTypeHolder::createYes(new Variable($parameter->getName()), $ifType));
1769+
$conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
1770+
}
1771+
1772+
$elseConditionTypes = $elseConditionType instanceof UnionType ? $elseConditionType->getTypes() : [$elseConditionType];
1773+
foreach ($elseConditionTypes as $conditionType) {
1774+
$holder = new ConditionalExpressionHolder([
1775+
$parameterType->getParameterName() => ExpressionTypeHolder::createYes(new Variable($targetParameterName), $conditionType),
1776+
], ExpressionTypeHolder::createYes(new Variable($parameter->getName()), $elseType));
1777+
$conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
1778+
}
17701779
}
17711780
}
17721781

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php // lint >= 8.0
2+
3+
declare(strict_types = 1);
4+
5+
namespace Bug10055;
6+
7+
use function PHPStan\Testing\assertType;
8+
9+
/**
10+
* @param 'value1'|'value2'|'value3' $param1
11+
* @param ($param1 is 'value3' ? bool : int) $param2
12+
*/
13+
function test(string $param1, int|bool $param2): void
14+
{
15+
match ($param1) {
16+
'value1' => assertType('int', $param2),
17+
'value2' => assertType('int', $param2),
18+
'value3' => assertType('bool', $param2),
19+
};
20+
}

0 commit comments

Comments
 (0)