Skip to content

Commit 151b7b8

Browse files
phpstan-botclaude
andcommitted
Use isSuperTypeOf for condition matching in conditional parameter types
Instead of splitting union condition types into individual ConditionalExpressionHolder instances, use isSuperTypeOf at the matching site so that a narrowed type (e.g. 'value1') correctly matches a broader condition type (e.g. 'value1'|'value2'). A new useSubtypeForConditionMatching flag on ConditionalExpressionHolder limits this relaxed matching to conditional parameter type holders only. Other holders (from scope merging, assignments, type specifying) keep strict equals() matching to avoid cascading side effects. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e61bf3e commit 151b7b8

File tree

2 files changed

+29
-19
lines changed

2 files changed

+29
-19
lines changed

src/Analyser/ConditionalExpressionHolder.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class ConditionalExpressionHolder
1717
public function __construct(
1818
private array $conditionExpressionTypeHolders,
1919
private ExpressionTypeHolder $typeHolder,
20+
private bool $useSubtypeForConditionMatching = false,
2021
)
2122
{
2223
if (count($conditionExpressionTypeHolders) === 0) {
@@ -37,6 +38,11 @@ public function getTypeHolder(): ExpressionTypeHolder
3738
return $this->typeHolder;
3839
}
3940

41+
public function useSubtypeForConditionMatching(): bool
42+
{
43+
return $this->useSubtypeForConditionMatching;
44+
}
45+
4046
public function getKey(): string
4147
{
4248
$parts = [];

src/Analyser/MutatingScope.php

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

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-
}
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), true);
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), true);
1769+
$conditionalTypes['$' . $parameter->getName()][$holder->getKey()] = $holder;
17791770
}
17801771
}
17811772

@@ -3227,9 +3218,22 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
32273218
}
32283219
foreach ($conditionalExpressions as $conditionalExpression) {
32293220
foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
3230-
if (!array_key_exists($holderExprString, $specifiedExpressions) || !$specifiedExpressions[$holderExprString]->equals($conditionalTypeHolder)) {
3221+
if (!array_key_exists($holderExprString, $specifiedExpressions)) {
32313222
continue 2;
32323223
}
3224+
$specifiedHolder = $specifiedExpressions[$holderExprString];
3225+
if (!$specifiedHolder->getCertainty()->equals($conditionalTypeHolder->getCertainty())) {
3226+
continue 2;
3227+
}
3228+
if ($conditionalExpression->useSubtypeForConditionMatching()) {
3229+
if (!$conditionalTypeHolder->getType()->isSuperTypeOf($specifiedHolder->getType())->yes()) {
3230+
continue 2;
3231+
}
3232+
} else {
3233+
if (!$specifiedHolder->equals($conditionalTypeHolder)) {
3234+
continue 2;
3235+
}
3236+
}
32333237
}
32343238

32353239
$conditions[$conditionalExprString][] = $conditionalExpression;

0 commit comments

Comments
 (0)