Skip to content

Commit aa1f96e

Browse files
phpstan-botclaude
andcommitted
Add comments explaining the two-pass condition matching in filterBySpecifiedTypes
Pass 1 uses exact equals() matching. Pass 2 falls back to isSuperTypeOf() for cases where the condition type is broader than the narrowed expression type (e.g. union condition types from conditional parameter types or scope merging). Pass 1 takes priority to prevent broader conditions from degrading certainty calculations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent df3ed28 commit aa1f96e

1 file changed

Lines changed: 18 additions & 0 deletions

File tree

src/Analyser/MutatingScope.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3217,6 +3217,8 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
32173217
continue;
32183218
}
32193219

3220+
// Pass 1: Exact match - condition type must equal the specified expression type.
3221+
// This handles most cases and takes priority over Pass 2.
32203222
foreach ($conditionalExpressions as $conditionalExpression) {
32213223
foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
32223224
if (
@@ -3235,6 +3237,22 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
32353237
continue;
32363238
}
32373239

3240+
// Pass 2: Supertype match - condition type must be a supertype of the specified
3241+
// expression type. Only runs when Pass 1 found no exact match for this expression.
3242+
//
3243+
// This is needed for conditional parameter types with union conditions:
3244+
// e.g. @param ($p is 'a' ? bool : int) with $p typed as 'a'|'b'|'c'
3245+
// creates a condition type 'b'|'c' (from TypeCombinator::remove) for the int result.
3246+
// When $p is narrowed to 'b', equals('b', 'b'|'c') fails but isSuperTypeOf succeeds.
3247+
//
3248+
// Also needed for scope merging: e.g. $a=0 then if (is_string||is_int) $a=1
3249+
// creates a condition type int|string. When narrowed to is_int, the condition
3250+
// int|string is a supertype of int, so the match succeeds.
3251+
//
3252+
// Pass 1 must take priority because when both exact and broader conditions exist
3253+
// (e.g. from scope merging creating both "$key=2 => $value Yes" and
3254+
// "$key=0|2 => $value Maybe"), the broader match would degrade certainty
3255+
// via extremeIdentity (Yes AND Maybe = Maybe), causing false positives.
32383256
foreach ($conditionalExpressions as $conditionalExpression) {
32393257
foreach ($conditionalExpression->getConditionExpressionTypeHolders() as $holderExprString => $conditionalTypeHolder) {
32403258
if (

0 commit comments

Comments
 (0)