From b9b16bef45bf2341adb9f8be6c681a51fc002c89 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:59:42 +0000 Subject: [PATCH 1/4] Fix phpstan/phpstan#14351: Missing errors around use of $this - Emit VariableAssignNode for catch variable in NodeScopeResolver so InvalidVariableAssignRule detects `catch (Exception $this)` - Add ThisInGlobalStatementRule to report `global $this` usage - New regression test in tests/PHPStan/Rules/Variables/data/bug-14351.php --- src/Analyser/NodeScopeResolver.php | 1 + .../Variables/ThisInGlobalStatementRule.php | 48 +++++++++++++++++++ .../InvalidVariableAssignRuleTest.php | 10 ++++ .../ThisInGlobalStatementRuleTest.php | 29 +++++++++++ .../Rules/Variables/data/bug-14351.php | 16 +++++++ 5 files changed, 104 insertions(+) create mode 100644 src/Rules/Variables/ThisInGlobalStatementRule.php create mode 100644 tests/PHPStan/Rules/Variables/ThisInGlobalStatementRuleTest.php create mode 100644 tests/PHPStan/Rules/Variables/data/bug-14351.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8935ade8e2c..c32589e98ef 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1974,6 +1974,7 @@ public function processStmtNode( } $variableName = $catchNode->var->name; + $this->callNodeCallback($nodeCallback, new VariableAssignNode($catchNode->var, $catchNode->var), $scope, $storage); } $catchScopeResult = $this->processStmtNodesInternal($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $storage, $nodeCallback, $context); diff --git a/src/Rules/Variables/ThisInGlobalStatementRule.php b/src/Rules/Variables/ThisInGlobalStatementRule.php new file mode 100644 index 00000000000..99e7918367d --- /dev/null +++ b/src/Rules/Variables/ThisInGlobalStatementRule.php @@ -0,0 +1,48 @@ + + */ +#[RegisteredRule(level: 0)] +final class ThisInGlobalStatementRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Stmt\Global_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($node->vars as $var) { + if (!$var instanceof Variable) { + continue; + } + if (!is_string($var->name)) { + continue; + } + if ($var->name !== 'this') { + continue; + } + + $errors[] = RuleErrorBuilder::message('Cannot use $this as global variable.') + ->identifier('global.this') + ->nonIgnorable() + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Variables/InvalidVariableAssignRuleTest.php b/tests/PHPStan/Rules/Variables/InvalidVariableAssignRuleTest.php index 77cbf2c1b47..47df063cefb 100644 --- a/tests/PHPStan/Rules/Variables/InvalidVariableAssignRuleTest.php +++ b/tests/PHPStan/Rules/Variables/InvalidVariableAssignRuleTest.php @@ -88,6 +88,16 @@ public function testBug14352(): void ]); } + public function testBug14351(): void + { + $this->analyse([__DIR__ . '/data/bug-14351.php'], [ + [ + 'Cannot re-assign $this.', + 9, + ], + ]); + } + public function testBug14349(): void { $this->analyse([__DIR__ . '/data/bug-14349.php'], [ diff --git a/tests/PHPStan/Rules/Variables/ThisInGlobalStatementRuleTest.php b/tests/PHPStan/Rules/Variables/ThisInGlobalStatementRuleTest.php new file mode 100644 index 00000000000..85ba69cc384 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/ThisInGlobalStatementRuleTest.php @@ -0,0 +1,29 @@ + + */ +class ThisInGlobalStatementRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new ThisInGlobalStatementRule(); + } + + public function testBug14351(): void + { + $this->analyse([__DIR__ . '/data/bug-14351.php'], [ + [ + 'Cannot use $this as global variable.', + 15, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-14351.php b/tests/PHPStan/Rules/Variables/data/bug-14351.php new file mode 100644 index 00000000000..b8b3ba6ad41 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/bug-14351.php @@ -0,0 +1,16 @@ + Date: Mon, 23 Mar 2026 11:17:47 +0000 Subject: [PATCH 2/4] Add bug-14351.php to make-lint exclude list The test data file contains intentionally invalid PHP (catch $this, global $this) that triggers lint errors, so it needs to be excluded. Co-Authored-By: Claude Opus 4.6 --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 5fdb6664482..a0b15cef22e 100644 --- a/Makefile +++ b/Makefile @@ -135,6 +135,7 @@ lint: --exclude tests/PHPStan/Rules/Functions/data/bug-14241.php \ --exclude tests/PHPStan/Rules/Variables/data/bug-14349.php \ --exclude tests/PHPStan/Rules/Variables/data/bug-14352.php \ + --exclude tests/PHPStan/Rules/Variables/data/bug-14351.php \ src tests install-paratest: From 398df87b507f955347c9a353da31a771cb3f19bf Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Mon, 23 Mar 2026 18:52:05 +0000 Subject: [PATCH 3/4] Also detect `static $this` as fatal error Add ThisInStaticStatementRule to report "Cannot use $this as static variable." alongside the existing global $this and catch $this checks. Co-Authored-By: Claude Opus 4.6 --- .../Variables/ThisInStaticStatementRule.php | 44 +++++++++++++++++++ .../ThisInStaticStatementRuleTest.php | 29 ++++++++++++ .../Rules/Variables/data/bug-14351.php | 4 ++ 3 files changed, 77 insertions(+) create mode 100644 src/Rules/Variables/ThisInStaticStatementRule.php create mode 100644 tests/PHPStan/Rules/Variables/ThisInStaticStatementRuleTest.php diff --git a/src/Rules/Variables/ThisInStaticStatementRule.php b/src/Rules/Variables/ThisInStaticStatementRule.php new file mode 100644 index 00000000000..d6a36d11293 --- /dev/null +++ b/src/Rules/Variables/ThisInStaticStatementRule.php @@ -0,0 +1,44 @@ + + */ +#[RegisteredRule(level: 0)] +final class ThisInStaticStatementRule implements Rule +{ + + public function getNodeType(): string + { + return Node\Stmt\Static_::class; + } + + public function processNode(Node $node, Scope $scope): array + { + $errors = []; + foreach ($node->vars as $var) { + if (!is_string($var->var->name)) { + continue; + } + if ($var->var->name !== 'this') { + continue; + } + + $errors[] = RuleErrorBuilder::message('Cannot use $this as static variable.') + ->identifier('static.this') + ->nonIgnorable() + ->build(); + } + + return $errors; + } + +} diff --git a/tests/PHPStan/Rules/Variables/ThisInStaticStatementRuleTest.php b/tests/PHPStan/Rules/Variables/ThisInStaticStatementRuleTest.php new file mode 100644 index 00000000000..2e538877780 --- /dev/null +++ b/tests/PHPStan/Rules/Variables/ThisInStaticStatementRuleTest.php @@ -0,0 +1,29 @@ + + */ +class ThisInStaticStatementRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return new ThisInStaticStatementRule(); + } + + public function testBug14351(): void + { + $this->analyse([__DIR__ . '/data/bug-14351.php'], [ + [ + 'Cannot use $this as static variable.', + 19, + ], + ]); + } + +} diff --git a/tests/PHPStan/Rules/Variables/data/bug-14351.php b/tests/PHPStan/Rules/Variables/data/bug-14351.php index b8b3ba6ad41..6d9745a0eff 100644 --- a/tests/PHPStan/Rules/Variables/data/bug-14351.php +++ b/tests/PHPStan/Rules/Variables/data/bug-14351.php @@ -14,3 +14,7 @@ function foo(): void { function foo(): void { global $this; // should report: Cannot use $this as global variable } + +function bar(): void { + static $this; // should report: Cannot use $this as static variable +} From d98b140f4fbece0b56888141e92eb5e6b4aba187 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Tue, 24 Mar 2026 08:08:34 +0000 Subject: [PATCH 4/4] Use catch type as assigned expression in VariableAssignNode for catch variables The VariableAssignNode for catch variables was using the variable itself as the assigned expression, which meant ParameterOutAssignedTypeRule could not detect type mismatches when a catch variable overwrites a by-ref parameter. Now uses TypeExpr wrapping the catch type so the parameterByRef.type check works correctly. Co-Authored-By: Claude Opus 4.6 --- src/Analyser/NodeScopeResolver.php | 2 +- .../ParameterOutAssignedTypeRuleTest.php | 15 +++++++++++++ .../data/parameter-out-catch-variable.php | 22 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Rules/Variables/data/parameter-out-catch-variable.php diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index c32589e98ef..ca1daa6f340 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -1974,7 +1974,7 @@ public function processStmtNode( } $variableName = $catchNode->var->name; - $this->callNodeCallback($nodeCallback, new VariableAssignNode($catchNode->var, $catchNode->var), $scope, $storage); + $this->callNodeCallback($nodeCallback, new VariableAssignNode($catchNode->var, new TypeExpr($catchType)), $scope, $storage); } $catchScopeResult = $this->processStmtNodesInternal($catchNode, $catchNode->stmts, $catchScope->enterCatchType($catchType, $variableName), $storage, $nodeCallback, $context); diff --git a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php index 6da45dd2485..9587af37e21 100644 --- a/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php +++ b/tests/PHPStan/Rules/Variables/ParameterOutAssignedTypeRuleTest.php @@ -91,4 +91,19 @@ public function testBug14124b(): void $this->analyse([__DIR__ . '/data/bug-14124b.php'], []); } + public function testCatchVariable(): void + { + $this->analyse([__DIR__ . '/data/parameter-out-catch-variable.php'], [ + [ + 'Parameter &$p @param-out type of function ParameterOutCatchVariable\foo() expects int, Exception given.', + 11, + ], + [ + 'Parameter &$p by-ref type of function ParameterOutCatchVariable\bar() expects int, Exception given.', + 19, + 'You can change the parameter out type with @param-out PHPDoc tag.', + ], + ]); + } + } diff --git a/tests/PHPStan/Rules/Variables/data/parameter-out-catch-variable.php b/tests/PHPStan/Rules/Variables/data/parameter-out-catch-variable.php new file mode 100644 index 00000000000..6603c47d97a --- /dev/null +++ b/tests/PHPStan/Rules/Variables/data/parameter-out-catch-variable.php @@ -0,0 +1,22 @@ +