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
3 changes: 2 additions & 1 deletion src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,8 @@ 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)
)
&& ($lookForTrait === null || $traitFound)
) {
$parentNameScope = array_last($typeMapStack) ?? null;
$typeAliasesMap = array_last($typeAliasStack) ?? [];
Expand Down
29 changes: 29 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-11145.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php // lint >= 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);
}
42 changes: 42 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-12639.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php declare(strict_types=1);

namespace Bug12639\Database\FieldTypes;

/** @template T */
class ObjectRefT {}

namespace Bug12639\Accounts;

class Account {}

namespace Bug12639\Policy;

if (!defined('Bug12639')) {
define('Bug12639', true);
}

use Bug12639\Database\FieldTypes;
use Bug12639\Accounts\Account;

use function PHPStan\Testing\assertType;

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

protected function test(): void
{
assertType('Bug12639\Database\FieldTypes\ObjectRefT<Bug12639\Accounts\Account>', $this->account);
}
}

class StandardAccount
{
use BaseAccount;

public function run(): void
{
assertType('Bug12639\Database\FieldTypes\ObjectRefT<Bug12639\Accounts\Account>', $this->account);
}
}
19 changes: 19 additions & 0 deletions tests/PHPStan/Analyser/nsrt/bug-8950.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Bug8950 {
interface FooInterface
{
}
}

namespace {

define('BUG_8950_FOO', 'foo');

use Bug8950\FooInterface;

/** @var FooInterface $foo */
$foo = null;

\PHPStan\Testing\assertType('Bug8950\FooInterface', $foo);
}
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