Skip to content

Commit 27e7d59

Browse files
phpstan-botclaude
andcommitted
Add comment explaining why instanceof UnionType is the correct guard
The `instanceof UnionType` check in filterBySpecifiedTypes is a structural check (is this type an explicit union of alternatives?), not a semantic type check (is this a string/int/etc?). It's the correct restriction because: - Union types represent disjunctions: narrowing to one member validly satisfies the guard condition (e.g., 1|2 narrowed to 1) - Non-union types like IntersectionType can have coincidental subtype relationships that cause incorrect type narrowing when the conditional result is applied (e.g., non-falsy-string with 'filter' as subtype) - BooleanType (which represents true|false but is not a UnionType) must be excluded because template conditional types create per-branch conditionals that would conflict via subtype matching Alternative approaches explored and rejected: - TypeCombinator::remove-based check: correctly handles unions and intersections, but incorrectly allows BooleanType guards (causing *NEVER* type in template conditional assertions like bug-14249) - !instanceof MixedType: too permissive, allows intersection type guards - getFiniteTypes() > 1: too restrictive, excludes non-finite unions like int|string Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c90cbe5 commit 27e7d59

File tree

1 file changed

+7
-0
lines changed

1 file changed

+7
-0
lines changed

src/Analyser/MutatingScope.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3227,6 +3227,13 @@ public function filterBySpecifiedTypes(SpecifiedTypes $specifiedTypes): self
32273227
continue;
32283228
}
32293229
$guardType = $conditionalTypeHolder->getType();
3230+
// Allow subtype matching: when the specified type is a strict subtype
3231+
// of the guard, fire the conditional. Restricted to UnionType guards
3232+
// because unions represent explicit disjunctions of alternatives —
3233+
// narrowing to one alternative validly satisfies the guard condition.
3234+
// Non-union types (IntersectionType, BooleanType, etc.) can have
3235+
// coincidental subtype relationships that cause incorrect type
3236+
// narrowing when the conditional result is applied to the scope.
32303237
if (
32313238
$conditionalExpression->getTypeHolder()->getCertainty()->yes()
32323239
&& $specifiedHolder->getCertainty()->yes()

0 commit comments

Comments
 (0)