Skip to content

Fix phpstan/phpstan#9349: Throw point are not properly recognized#5166

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

Fix phpstan/phpstan#9349: Throw point are not properly recognized#5166
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-0412zqj

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When a try block contains a method with @throws RuntimeException followed by other method calls (like $pdo->query()), and the catch block catches PDOException (which extends RuntimeException), PHPStan incorrectly reported $sql as "Undefined variable" instead of "Variable $sql might not be defined". This was because implicit throw points from other method calls were excluded from the catch scope.

Changes

  • Modified the implicit throw point matching logic in src/Analyser/NodeScopeResolver.php (Phase 3 of try-catch processing)
  • Added a $hasDirectExplicitNonThrowMatch flag that tracks whether any explicit @throws annotation has a definitive (yes) type match with the catch type
  • Phase 3 (implicit throw points) now runs unless there is a definitive explicit match, rather than being skipped whenever any explicit non-throw-expression match exists
  • Added regression test tests/PHPStan/Rules/Variables/data/bug-9349.php and corresponding test method in DefinedVariableRuleTest

Root cause

The try-catch throw point matching has three phases: (1) Throwable catches all, (2) explicit @throws matching, (3) implicit throw point matching. Phase 3 was guarded by a condition that skipped it when explicit non-throw-expression matches existed (!$onlyExplicitIsThrow). The problem was that @throws RuntimeException on maybeThrows() "maybe" matched PDOException (since PDOException extends RuntimeException), causing Phase 3 to be skipped entirely. This excluded the implicit throw point from $pdo->query() (which executes after $sql is assigned) from the catch scope.

The fix adds tracking of whether the explicit match is definitive (the throw type IS a subtype of the catch type, i.e. isSuperTypeOf returns yes) vs merely possible (maybe). Phase 3 is only skipped when there's a definitive explicit match, ensuring implicit throw points are included when the explicit @throws type only partially overlaps with the catch type.

Test

Added tests/PHPStan/Rules/Variables/data/bug-9349.php that reproduces the original issue: a try block with $this->maybeThrows() (annotated @throws RuntimeException), then $sql assignment, then $pdo->query($sql), caught by PDOException. The test verifies PHPStan reports "Variable $sql might not be defined" instead of "Undefined variable: $sql".

Fixes phpstan/phpstan#9349

- When a method has @throws with a supertype of the caught exception (e.g. @throws RuntimeException with catch PDOException), implicit throw points from other method calls were incorrectly excluded from the catch scope
- Phase 3 (implicit throw point matching) was skipped when explicit @throws matched even as "maybe", now it only skips when there's a definitive "yes" match
- Added regression test in tests/PHPStan/Rules/Variables/data/bug-9349.php

Closes phpstan/phpstan#9349
@VincentLanglet VincentLanglet force-pushed the create-pull-request/patch-0412zqj branch from 3984d04 to b6073f0 Compare March 15, 2026 14:41
@phpstan-bot
Copy link
Collaborator Author

Already retrieved the results - all 11,578 tests passed. Nothing more to do.

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.

3 participants