Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) ?? [];
Expand Down
55 changes: 55 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-12639.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace Bug12639\Types;

/** @template T */
class ObjectRefT
{
/** @var T */
public $value;
}

namespace Bug12639\Accounts;

class Account
{
}

namespace Bug12639\Policy;

use Bug12639\Types\ObjectRefT;
use Bug12639\Accounts\Account;
use function PHPStan\Testing\assertType;

trait BaseAccount
{
/**
* @var ObjectRefT<Account>
*/
protected ObjectRefT $account;
}

class StandardAccount
{
use BaseAccount;

public function doTest(): void
{
assertType('Bug12639\Types\ObjectRefT<Bug12639\Accounts\Account>', $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<Bug12639\Accounts\Account>', $this->account);
}
}
34 changes: 34 additions & 0 deletions tests/PHPStan/Type/FileTypeMapperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,40 @@ public function testFileWithCyclicPhpDocs(): void
$this->assertSame('CyclicPhpDocs\Foo|iterable<CyclicPhpDocs\Foo>', $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<Account>
*/',
);

$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<Bug12639Separate\Accounts\Account>',
$varTags[0]->getType()->describe(VerbosityLevel::precise()),
);
}

public function testFilesWithIdenticalPhpDocsUsingDifferentAliases(): void
{
/** @var FileTypeMapper $fileTypeMapper */
Expand Down
10 changes: 10 additions & 0 deletions tests/PHPStan/Type/data/bug-12639-class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php declare(strict_types = 1);

namespace Bug12639Separate\OtherPlace;

use Bug12639Separate\Policy\BaseAccount;

class StandardAccount
{
use BaseAccount;
}
18 changes: 18 additions & 0 deletions tests/PHPStan/Type/data/bug-12639-trait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);

namespace Bug12639Separate\Policy;

if (true) {
// some statement before use declarations
}

use Bug12639Separate\Types\ObjectRefT;
use Bug12639Separate\Accounts\Account;

trait BaseAccount
{
/**
* @var ObjectRefT<Account>
*/
protected ObjectRefT $account;
}
Loading