Skip to content

Fix phpstan/phpstan#7718: "Variable might not be defined" in second identical if block#5331

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

Fix phpstan/phpstan#7718: "Variable might not be defined" in second identical if block#5331
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-cu6sb3a

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When two identical if statements appear in sequence, and the first defines a variable, PHPStan incorrectly reports "Variable might not be defined" in the second if block. This happens specifically with == (loose comparison) where one side is mixed, because the TypeSpecifier doesn't narrow the sub-expression types, resulting in no type guards being created during scope merging.

Changes

  • Modified src/Analyser/TypeSpecifier.php: In resolveEqual's general fallback case, also track the condition expression itself via handleDefaultTruthyOrFalseyContext. This narrows the comparison expression from bool to true/false in the truthy/falsy scopes, creating a type guard that enables conditional expressions for variables defined under the condition.
  • The fix includes a safety check: only applies when both sides of the comparison produce non-empty SpecifiedTypes (i.e., both sub-expressions are deterministic/pure), preventing false negatives for expressions involving impure function calls like rand().
  • Updated tests/PHPStan/Analyser/TypeSpecifierTest.php to expect the new condition expression in the specified types output.
  • Added tests/PHPStan/Rules/Variables/data/bug-7718.php regression test.
  • Added test method testBug7718 in tests/PHPStan/Rules/Variables/DefinedVariableRuleTest.php.

Root cause

After if ($cond) { $var = 1; }, the truthy scope (where $var is defined) merges with the falsy scope (where it isn't), making $var "maybe defined". For the second if ($cond) to restore $var's certainty, the merge must create conditional expressions linking $var to a type guard — an expression whose type differs between the truthy and falsy branches.

With ===, the TypeSpecifier narrows $_GET['something'] to 'banana', creating a type difference that serves as a type guard. With == and mixed, no meaningful type narrowing occurs, so no type guard exists and no conditional expression is created.

The fix tracks the condition expression itself (e.g., $_GET['something'] == 'banana') in the scope, narrowing it to true in the truthy branch and false in the falsy branch. This creates the type difference needed for the conditional expression mechanism to work.

Test

Added tests/PHPStan/Rules/Variables/data/bug-7718.php which reproduces the original issue: two identical if ($_GET['something'] == 'banana') blocks where the first defines $bananas and the second uses it. The test expects no errors (empty error array).

Fixes phpstan/phpstan#7718

…ntical if block

- In resolveEqual's general fallback, also track the condition expression itself
  via handleDefaultTruthyOrFalseyContext so it serves as a type guard during
  scope merging, enabling conditional expressions for variables defined under
  the condition
- Only applied when both sides of the comparison are deterministic (non-impure)
- Updated TypeSpecifierTest to expect the new condition expression tracking
- Added regression test in tests/PHPStan/Rules/Variables/data/bug-7718.php
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.

2 participants