Skip to content

Fix phpstan/phpstan#9444: return statement is missing#5307

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

Fix phpstan/phpstan#9444: return statement is missing#5307
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-cmps7gw

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

PHPStan reported false positive "return statement is missing" errors for goto-based retry patterns where a return inside a try block was followed by a catch that used goto to retry. The fix treats goto as an always-terminating statement since it transfers control flow, making code after it unreachable.

Changes

  • Added Goto_ handling in src/Analyser/NodeScopeResolver.php (around line 910) to return isAlwaysTerminating = true with no exit points
  • Added regression test in tests/PHPStan/Rules/Missing/data/bug-9444.php with three goto-based retry patterns
  • Added test method testBug9444 in tests/PHPStan/Rules/Missing/MissingReturnRuleTest.php

Root cause

PHPStan's NodeScopeResolver had no handling for Goto_ statements at all. When processing statements sequentially, a goto was effectively treated as a no-op, meaning code after goto was considered reachable. This caused false positives in patterns like:

beginning:
try {
    return riskyOperation();
} catch (Throwable $e) {
    if ($retries-- > 0) {
        goto beginning;  // PHPStan didn't know this transfers control
    } else {
        throw $e;
    }
}

The catch block's if statement was not recognized as always-terminating because the goto branch wasn't terminating. This propagated up: the catch wasn't always-terminating, so the try/catch wasn't, so the function body wasn't, triggering the missing return error.

The fix is minimal: treat goto as always-terminating (like return, break, continue) since it unconditionally transfers control to a label, making any code after it unreachable.

Test

Added tests/PHPStan/Rules/Missing/data/bug-9444.php with three test cases:

  1. gotoRetry() - goto in catch with conditional retry and throw on exhaustion
  2. gotoRetrySimple() - unconditional goto retry in catch
  3. gotoInCatch() - goto in catch where code after try/catch is reachable (verifies no false negatives)

All expect zero return.missing errors.

Fixes phpstan/phpstan#9444

- Added handling for Goto_ in NodeScopeResolver to mark it as always
  terminating, since goto transfers control and makes subsequent code
  unreachable
- New regression test in tests/PHPStan/Rules/Missing/data/bug-9444.php
  covering goto-based retry patterns with try/return/catch
- The root cause was that PHPStan had no handling for goto statements,
  so code after goto was considered reachable, causing false positive
  "return statement is missing" errors in goto-based retry patterns
@VincentLanglet
Copy link
Copy Markdown
Contributor

I'm not sure fixing goto is a priority since such fix will still have the wrong inferences for the variables types.

Also I wonder if this "quick fix" won't trigger some other false positive like

public function foo(): void
{
     goto a;
     a:
     echo 'Bar';
}

reporting unreachable code.

This will need some tests.

@ondrejmirtes
Copy link
Copy Markdown
Member

goto needs similar handling as loops have (multiple iterations, generalizing scope)

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.

4 participants