Skip to content

Fix phpstan/phpstan#14421: Incorrect type narrowing of superglobal with dependent types#5376

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-rs78hg2
Open

Fix phpstan/phpstan#14421: Incorrect type narrowing of superglobal with dependent types#5376
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-rs78hg2

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a variable was assigned from a superglobal array offset in one branch and from a function return in another, narrowing that variable (e.g., $b !== null) would incorrectly propagate back to the superglobal, causing PHPStan to think the offset always exists.

Changes

  • Modified MutatingScope::createConditionalExpressions() in src/Analyser/MutatingScope.php to skip superglobal-containing expressions from being guarded in conditional expressions
  • Added superglobal detection (using the same NodeFinder + isGlobalVariable pattern already used in mergeVariableHolders()) to both loops that create conditional expression holders
  • New regression test in tests/PHPStan/Rules/Variables/IssetRuleTest.php and tests/PHPStan/Rules/Variables/data/bug-14421.php

Root cause

createConditionalExpressions() creates type guards that link expressions across merged scopes. When $_SESSION['a'] was checked with isset() in one branch and $b was assigned from it, the method created a conditional expression saying "when $b has type X, then $_SESSION has narrowed type Y". Later, when $b !== null was checked, this conditional triggered and incorrectly narrowed $_SESSION to always have offset 'a' set.

The fix extends the superglobal protection already present in mergeVariableHolders() to also apply in createConditionalExpressions(), preventing superglobal expressions from being targets of conditional type narrowing.

Test

Added a regression test reproducing the exact scenario from the issue: isset($_SESSION['a']) in an if/else with $b assigned from the superglobal in one branch and get_optional_int() in the other, followed by $b !== null check and inner !isset($_SESSION['a']). The test expects no errors (the false positive about offset 'a' always existing is gone).

Fixes phpstan/phpstan#14421

- Superglobal-containing expressions are now excluded from being guarded
  in conditional expressions during scope merging
- The root cause was that createConditionalExpressions() would create
  type dependencies linking superglobal array offsets to assigned variables,
  causing narrowing of the variable to incorrectly propagate back to the
  superglobal
- New regression test in tests/PHPStan/Rules/Variables/data/bug-14421.php

Closes phpstan/phpstan#14421
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant