diff --git a/src/Type/FileTypeMapper.php b/src/Type/FileTypeMapper.php index 2c281f58ca..006ce440a6 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_) ) ) && !array_key_exists($nameScopeKey, $nameScopeMap) + && ($lookForTrait === null || $traitFound) ) { $parentNameScope = array_last($typeMapStack) ?? null; $typeAliasesMap = array_last($typeAliasStack) ?? []; diff --git a/tests/PHPStan/Analyser/nsrt/bug-12639.php b/tests/PHPStan/Analyser/nsrt/bug-12639.php new file mode 100644 index 0000000000..ada55f669a --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/bug-12639.php @@ -0,0 +1,55 @@ + + */ + protected ObjectRefT $account; +} + +class StandardAccount +{ + use BaseAccount; + + public function doTest(): void + { + assertType('Bug12639\Types\ObjectRefT', $this->account); + } +} + +namespace Bug12639\OtherPlace; + +use Bug12639\Policy\BaseAccount; +use function PHPStan\Testing\assertType; + +class AnotherUser +{ + use BaseAccount; + + public function doTest(): void + { + assertType('Bug12639\Types\ObjectRefT', $this->account); + } +} 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; +}