Skip to content

[DeadCode] Keep type-assertion guard in BooleanAnd when right operand reuses the variable#8112

Closed
TomasVotruba wants to merge 1 commit into
mainfrom
keep-type-guard-in-boolean-and
Closed

[DeadCode] Keep type-assertion guard in BooleanAnd when right operand reuses the variable#8112
TomasVotruba wants to merge 1 commit into
mainfrom
keep-type-guard-in-boolean-and

Conversation

@TomasVotruba

@TomasVotruba TomasVotruba commented Jun 29, 2026

Copy link
Copy Markdown
Member

Bug

RemoveAlwaysTrueIfConditionRector strips the left operand of a && condition when it is "always true". For a type-assertion guard (is_string, is_array, ...) whose "always true" comes from a phpdoc-narrowed type, that type can be violated at runtime — and the right operand often depends on the guard.

Reported from spiral/framework (PrototypeBootloader::initRegistry), which had to skip the rule:

// $shortcut comes from getBindings(), which reads user config (runtime-wide)
if (\is_string($shortcut) && (\class_exists($shortcut, true) || \interface_exists($shortcut, true))) {
    $registry->bindProperty($property, $shortcut);
}

PHPStan narrows $shortcut to string (an earlier is_array($shortcut) && isset($shortcut['resolve']) branch removes the array shape via the @psalm-type), so is_string() is "always true" and the rule removed it:

if (\class_exists($shortcut, true) || \interface_exists($shortcut, true)) {

A malformed (non-string) binding in user config then reaches class_exists(array)TypeError. The is_string() guard exists precisely for that runtime case.

Fix

In the BooleanAnd path, keep the condition when the left operand is a type-assertion guard (is_string/is_array/is_int/...) whose argument variable is reused in the right operand. In that case the guard protects the right side, so it must not be dropped even if "always true" by declared types.

When the guarded variable is not reused on the right, stripping still happens.

Fixtures

  • skip_type_guard_reused_in_boolean_and.php.inc (no-change) — is_object($o) && method_exists($o, ...). This was previously the boolean_and.php.inc change fixture; the guard is now kept.
  • boolean_and_guard_not_reused.php.inc (change) — is_object($o) && $flag still strips, since $o is not reused.

@TomasVotruba TomasVotruba deleted the keep-type-guard-in-boolean-and branch June 29, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant