From 5626c008163913379893deba792cfc7901e50008 Mon Sep 17 00:00:00 2001 From: phpstan-bot <79867460+phpstan-bot@users.noreply.github.com> Date: Tue, 17 Feb 2026 05:09:30 +0000 Subject: [PATCH 1/4] Fix define() before use statements breaking PHPDoc class resolution - Removed !array_key_exists guard in FileTypeMapper::createPhpDocNodeMap() that prevented updating the name scope map entry after it was first set - The first non-skipped statement (e.g. define()) would lock in an empty $uses array for that scope key, causing later PHPDocs to fail resolving class names from use statements - New regression test in tests/PHPStan/Analyser/nsrt/bug-8950.php --- src/Type/FileTypeMapper.php | 2 +- tests/PHPStan/Analyser/nsrt/bug-8950.php | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-8950.php diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 2c281f58ca..87a9317962 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -541,7 +541,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA && !$node instanceof Node\Stmt\InlineHTML && !($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Include_) ) - ) && !array_key_exists($nameScopeKey, $nameScopeMap) + ) ) { $parentNameScope = array_last($typeMapStack) ?? null; $typeAliasesMap = array_last($typeAliasStack) ?? []; diff --git a/tests/PHPStan/Analyser/nsrt/bug-8950.php b/tests/PHPStan/Analyser/nsrt/bug-8950.php new file mode 100644 index 0000000000..4ae6dba684 --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-8950.php @@ -0,0 +1,19 @@ + Date: Tue, 17 Feb 2026 05:22:05 +0000 Subject: [PATCH 2/4] Add regression test for #11145 Closes https://github.com/phpstan/phpstan/issues/11145 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-11145.php | 29 +++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-11145.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-11145.php b/tests/PHPStan/Analyser/nsrt/bug-11145.php new file mode 100644 index 0000000000..2d9783be9c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-11145.php @@ -0,0 +1,29 @@ += 8.1 + +namespace Bug11145 { + class Bar {} +} + +namespace { + + define('BUG_11145_FOO', 'foo'); + + use Bug11145\Bar; + + class Bug11145Baz { + public function __construct(readonly public Bar $bar) {} + } + + class Bug11145Container { + public function get(string $id): object { + return new \stdClass(); + } + } + + /** @var Bar $bar */ + $bar = (new Bug11145Container())->get('anything'); + \PHPStan\Testing\assertType('Bug11145\Bar', $bar); + + $baz = new Bug11145Baz($bar); + \PHPStan\Testing\assertType('Bug11145Baz', $baz); +} From dc1ec91840cfeea5f8fa95c92b39952ef1cc343e Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Tue, 17 Feb 2026 05:23:10 +0000 Subject: [PATCH 3/4] Add regression test for #12639 Closes https://github.com/phpstan/phpstan/issues/12639 Co-Authored-By: Claude Opus 4.6 --- tests/PHPStan/Analyser/nsrt/bug-12639.php | 42 +++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/PHPStan/Analyser/nsrt/bug-12639.php diff --git a/tests/PHPStan/Analyser/nsrt/bug-12639.php b/tests/PHPStan/Analyser/nsrt/bug-12639.php new file mode 100644 index 0000000000..ea4644991c --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12639.php @@ -0,0 +1,42 @@ + */ + protected FieldTypes\ObjectRefT $account; + + protected function test(): void + { + assertType('Bug12639\Database\FieldTypes\ObjectRefT', $this->account); + } +} + +class StandardAccount +{ + use BaseAccount; + + public function run(): void + { + assertType('Bug12639\Database\FieldTypes\ObjectRefT', $this->account); + } +} From 10042b70f701ec4cdf044844ba97650b7ac795e6 Mon Sep 17 00:00:00 2001 From: phpstan-bot Date: Tue, 17 Feb 2026 06:09:05 +0000 Subject: [PATCH 4/4] Fix CI failures [claude-ci-fix] Automated fix attempt 1 for CI failures. --- src/Type/FileTypeMapper.php | 1 + tests/PHPStan/Type/FileTypeMapperTest.php | 34 +++++++++++++++++++++ tests/PHPStan/Type/data/bug-12639-class.php | 10 ++++++ tests/PHPStan/Type/data/bug-12639-trait.php | 18 +++++++++++ 4 files changed, 63 insertions(+) create mode 100644 tests/PHPStan/Type/data/bug-12639-class.php create mode 100644 tests/PHPStan/Type/data/bug-12639-trait.php diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 87a9317962..f83ca5ad18 100644 --- a/src/Type/FileTypeMapper.php +++ b/src/Type/FileTypeMapper.php @@ -542,6 +542,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA && !($node instanceof Node\Stmt\Expression && $node->expr instanceof Node\Expr\Include_) ) ) + && ($lookForTrait === null || $traitFound) ) { $parentNameScope = array_last($typeMapStack) ?? null; $typeAliasesMap = array_last($typeAliasStack) ?? []; diff --git a/tests/PHPStan/Type/FileTypeMapperTest.php b/tests/PHPStan/Type/FileTypeMapperTest.php index d613047140..fc77cd3aa3 100644 --- a/tests/PHPStan/Type/FileTypeMapperTest.php +++ b/tests/PHPStan/Type/FileTypeMapperTest.php @@ -196,6 +196,40 @@ public function testFileWithCyclicPhpDocs(): void $this->assertSame('CyclicPhpDocs\Foo|iterable', $returnTag->getType()->describe(VerbosityLevel::precise())); } + public function testBug12639TraitPropertyPhpDocResolution(): void + { + self::createReflectionProvider(); + + /** @var FileTypeMapper $fileTypeMapper */ + $fileTypeMapper = self::getContainer()->getByType(FileTypeMapper::class); + + $classRealpath = realpath(__DIR__ . '/data/bug-12639-class.php'); + if ($classRealpath === false) { + throw new ShouldNotHappenException(); + } + + // Simulate how PhpClassReflectionExtension calls getResolvedPhpDoc for a trait property + // When the trait is in a different file, $fileName is the class file + $resolved = $fileTypeMapper->getResolvedPhpDoc( + $classRealpath, + 'Bug12639Separate\OtherPlace\StandardAccount', + 'Bug12639Separate\Policy\BaseAccount', + null, + '/** + * @var ObjectRefT + */', + ); + + $varTags = $resolved->getVarTags(); + $this->assertArrayHasKey(0, $varTags); + // Account should resolve to Bug12639Separate\Accounts\Account (from the trait's use statement), + // not Bug12639Separate\OtherPlace\Account (class namespace) + $this->assertSame( + 'Bug12639Separate\Types\ObjectRefT', + $varTags[0]->getType()->describe(VerbosityLevel::precise()), + ); + } + public function testFilesWithIdenticalPhpDocsUsingDifferentAliases(): void { /** @var FileTypeMapper $fileTypeMapper */ diff --git a/tests/PHPStan/Type/data/bug-12639-class.php b/tests/PHPStan/Type/data/bug-12639-class.php new file mode 100644 index 0000000000..4031e90a4f --- /dev/null +++ b/tests/PHPStan/Type/data/bug-12639-class.php @@ -0,0 +1,10 @@ + + */ + protected ObjectRefT $account; +}