From f63627a60b37bf31c28a3da5ae7d37b0285c7f6b Mon Sep 17 00:00:00 2001 From: VincentLanglet <9052536+VincentLanglet@users.noreply.github.com> Date: Sat, 16 May 2026 19:44:59 +0000 Subject: [PATCH 1/3] Add regression test for circular class constant PHPDoc type references - Add nsrt test verifying correct type resolution when class constant PHPDoc types cross-reference each other (e.g. int<0, self::MAX> and int) - Add integration test proving the code completes without hanging, covering both the non-circular value case (literals) and the circular value case (which reports unresolvable type errors) - The underlying hang was fixed by the currentlyResolvingClassConstant guard in InitializerExprTypeResolver (commit 93af41bf5), but no regression test existed for the specific scenario from issue #9172 Co-Authored-By: Claude Opus 4.6 --- .../Analyser/AnalyserIntegrationTest.php | 8 ++++ tests/PHPStan/Analyser/data/bug-9172.php | 26 +++++++++++ tests/PHPStan/Analyser/nsrt/bug-9172.php | 43 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 tests/PHPStan/Analyser/data/bug-9172.php create mode 100644 tests/PHPStan/Analyser/nsrt/bug-9172.php diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 27b9f44c3d..f013fd790e 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1578,6 +1578,14 @@ public function testBug14596(): void $this->assertNotEmpty($errors); } + public function testBug9172(): void + { + $errors = $this->runAnalyse(__DIR__ . '/data/bug-9172.php'); + $this->assertCount(2, $errors); + $this->assertSame('PHPDoc tag @var for constant Bug9172Integration\CircularValues::MIN contains unresolvable type.', $errors[0]->getMessage()); + $this->assertSame('PHPDoc tag @var for constant Bug9172Integration\CircularValues::MAX contains unresolvable type.', $errors[1]->getMessage()); + } + /** * @param string[]|null $allAnalysedFiles * @return list diff --git a/tests/PHPStan/Analyser/data/bug-9172.php b/tests/PHPStan/Analyser/data/bug-9172.php new file mode 100644 index 0000000000..0118ca9438 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-9172.php @@ -0,0 +1,26 @@ + */ + public const MIN_DEPOSIT = 1_000; + + /** @var int */ + public const MAX_DEPOSIT = 20_000; + + /** @param int $amount */ + public function deposit(int $amount): void + { + } +} + +final class CircularValues +{ + /** @var int<0, self::MAX> */ + public const MIN = self::MAX - 19_000; + + /** @var int */ + public const MAX = self::MIN + 19_000; +} diff --git a/tests/PHPStan/Analyser/nsrt/bug-9172.php b/tests/PHPStan/Analyser/nsrt/bug-9172.php new file mode 100644 index 0000000000..0cc86e1753 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-9172.php @@ -0,0 +1,43 @@ + */ + public const MIN_DEPOSIT = 1_000; + + /** @var int */ + public const MAX_DEPOSIT = 20_000; +} + +abstract class AbstractDeposit +{ + /** @var int<0, self::MAX_DEPOSIT> */ + public const MIN_DEPOSIT = 1_000; + + /** @var int */ + public const MAX_DEPOSIT = 20_000; + + public function test(): void + { + assertType('int<0, 20000>', static::MIN_DEPOSIT); + assertType('int<1000, max>', static::MAX_DEPOSIT); + } +} + +final class DepositService +{ + /** @param int $amount */ + public function deposit(int $amount): void + { + assertType('int<1000, 20000>', $amount); + } +} + +function test(): void { + assertType('1000', HelloWorld::MIN_DEPOSIT); + assertType('20000', HelloWorld::MAX_DEPOSIT); +} From a507b60ee4d7da9f239b4d4af4769a7546ab0fad Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 16 May 2026 19:55:32 +0000 Subject: [PATCH 2/3] Simplify bug-9172 integration test to only assert no crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The test just needs to verify PHPStan doesn't hang on circular class constant PHPDoc references — asserting specific error messages is unnecessary. Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index f013fd790e..08b8f097f3 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1581,9 +1581,7 @@ public function testBug14596(): void public function testBug9172(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-9172.php'); - $this->assertCount(2, $errors); - $this->assertSame('PHPDoc tag @var for constant Bug9172Integration\CircularValues::MIN contains unresolvable type.', $errors[0]->getMessage()); - $this->assertSame('PHPDoc tag @var for constant Bug9172Integration\CircularValues::MAX contains unresolvable type.', $errors[1]->getMessage()); + $this->assertNotEmpty($errors); } /** From 1b518d9a8d888bdefdb0c64621ddb45b988cfddb Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Sat, 16 May 2026 20:21:15 +0000 Subject: [PATCH 3/3] Remove nsrt test for bug-9172, keep only integration test The integration test alone is sufficient to verify PHPStan doesn't hang on circular class constant PHPDoc type references. Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-9172.php | 43 ------------------------ 1 file changed, 43 deletions(-) delete mode 100644 tests/PHPStan/Analyser/nsrt/bug-9172.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-9172.php b/tests/PHPStan/Analyser/nsrt/bug-9172.php deleted file mode 100644 index 0cc86e1753..0000000000 --- a/tests/PHPStan/Analyser/nsrt/bug-9172.php +++ /dev/null @@ -1,43 +0,0 @@ - */ - public const MIN_DEPOSIT = 1_000; - - /** @var int */ - public const MAX_DEPOSIT = 20_000; -} - -abstract class AbstractDeposit -{ - /** @var int<0, self::MAX_DEPOSIT> */ - public const MIN_DEPOSIT = 1_000; - - /** @var int */ - public const MAX_DEPOSIT = 20_000; - - public function test(): void - { - assertType('int<0, 20000>', static::MIN_DEPOSIT); - assertType('int<1000, max>', static::MAX_DEPOSIT); - } -} - -final class DepositService -{ - /** @param int $amount */ - public function deposit(int $amount): void - { - assertType('int<1000, 20000>', $amount); - } -} - -function test(): void { - assertType('1000', HelloWorld::MIN_DEPOSIT); - assertType('20000', HelloWorld::MAX_DEPOSIT); -}