Skip to content

Commit 60021c2

Browse files
committed
Fix type aliases in traits
1 parent c926144 commit 60021c2

File tree

7 files changed

+250
-6
lines changed

7 files changed

+250
-6
lines changed

Diff for: src/Analyser/NameScope.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class NameScope
2828
* @param array<string, string> $constUses alias(string) => fullName(string)
2929
* @param array<string, true> $typeAliasesMap
3030
*/
31-
public function __construct(private ?string $namespace, private array $uses, private ?string $className = null, private ?string $functionName = null, ?TemplateTypeMap $templateTypeMap = null, private array $typeAliasesMap = [], private bool $bypassTypeAliases = false, private array $constUses = [])
31+
public function __construct(private ?string $namespace, private array $uses, private ?string $className = null, private ?string $functionName = null, ?TemplateTypeMap $templateTypeMap = null, private array $typeAliasesMap = [], private bool $bypassTypeAliases = false, private array $constUses = [], private ?string $typeAliasClassName = null)
3232
{
3333
$this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty();
3434
}
@@ -64,6 +64,11 @@ public function getClassName(): ?string
6464
return $this->className;
6565
}
6666

67+
public function getClassNameForTypeAlias(): ?string
68+
{
69+
return $this->typeAliasClassName ?? $this->className;
70+
}
71+
6772
public function resolveStringName(string $name): string
6873
{
6974
if (strpos($name, '\\') === 0) {

Diff for: src/Type/FileTypeMapper.php

+7-4
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
269269
}
270270

271271
$traitFound = true;
272+
$typeAliasStack[] = $this->getTypeAliasesMap($node->getDocComment());
273+
$functionStack[] = null;
272274
} else {
273275
if ($node->name === null) {
274276
if (!$node instanceof Node\Stmt\Class_) {
@@ -304,12 +306,12 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
304306
if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
305307
$phpDocString = GetLastDocComment::forNode($node);
306308
if ($phpDocString !== null) {
307-
$typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap {
309+
$typeMapStack[] = function () use ($namespace, $uses, $className, $lookForTrait, $functionName, $phpDocString, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap {
308310
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
309311
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;
310312
$currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null;
311313
$typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? [];
312-
$nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, $typeAliasesMap, false, $constUses);
314+
$nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, $typeAliasesMap, false, $constUses, $lookForTrait);
313315
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
314316
$templateTypeScope = $nameScope->getTemplateTypeScope();
315317
if ($templateTypeScope === null) {
@@ -355,6 +357,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
355357
$typeAliasesMap,
356358
false,
357359
$constUses,
360+
$lookForTrait,
358361
);
359362
}
360363

@@ -492,8 +495,8 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
492495

493496
return null;
494497
},
495-
static function (Node $node, $callbackResult) use ($lookForTrait, &$namespace, &$functionStack, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack, &$constUses): void {
496-
if ($node instanceof Node\Stmt\ClassLike && $lookForTrait === null) {
498+
static function (Node $node, $callbackResult) use (&$namespace, &$functionStack, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack, &$constUses): void {
499+
if ($node instanceof Node\Stmt\ClassLike) {
497500
if (count($classStack) === 0) {
498501
throw new ShouldNotHappenException();
499502
}

Diff for: src/Type/UsefulTypeAliasResolver.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
6969
return null;
7070
}
7171

72-
$className = $nameScope->getClassName();
72+
$className = $nameScope->getClassNameForTypeAlias();
7373
if ($className === null) {
7474
return null;
7575
}

Diff for: tests/PHPStan/Analyser/AnalyserIntegrationTest.php

+6
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,12 @@ public function testBug9008(): void
11701170
$this->assertNoErrors($errors);
11711171
}
11721172

1173+
public function testBug5091(): void
1174+
{
1175+
$errors = $this->runAnalyse(__DIR__ . '/data/bug-5091.php');
1176+
$this->assertNoErrors($errors);
1177+
}
1178+
11731179
/**
11741180
* @param string[]|null $allAnalysedFiles
11751181
* @return Error[]

Diff for: tests/PHPStan/Analyser/NodeScopeResolverTest.php

+1
Original file line numberDiff line numberDiff line change
@@ -1215,6 +1215,7 @@ public function dataFileAsserts(): iterable
12151215
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8956.php');
12161216
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8917.php');
12171217
yield from $this->gatherAssertTypes(__DIR__ . '/data/ds-copy.php');
1218+
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-type-alias.php');
12181219
}
12191220

12201221
/**

Diff for: tests/PHPStan/Analyser/data/bug-5091.php

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
3+
namespace Bug5091\Monolog {
4+
/**
5+
* @phpstan-type Record array{message: string}
6+
*/
7+
class Logger {
8+
}
9+
}
10+
11+
namespace Bug5091\Monolog\Handler {
12+
/**
13+
* @phpstan-import-type Record from \Bug5091\Monolog\Logger
14+
*/
15+
class Handler {
16+
use \Bug5091\Monolog\Processor\TraitA;
17+
}
18+
}
19+
20+
namespace Bug5091\Monolog\Processor {
21+
/**
22+
* @phpstan-import-type Record from \Bug5091\Monolog\Logger
23+
*/
24+
trait TraitA {
25+
/**
26+
* @var Record
27+
*/
28+
public $foo;
29+
30+
/**
31+
* @return Record
32+
*/
33+
public function foo() {
34+
return ['message' => ''];
35+
}
36+
}
37+
}
38+
39+
namespace Bug5091 {
40+
41+
/**
42+
* @phpstan-type MyType array{foobar: string}
43+
*/
44+
trait MyTrait
45+
{
46+
/**
47+
* @return array<MyType>
48+
*/
49+
public function MyMethod(): array
50+
{
51+
return [['foobar' => 'foo']];
52+
}
53+
}
54+
55+
class MyClass
56+
{
57+
use MyTrait;
58+
}
59+
60+
/**
61+
* @phpstan-type TypeArrayAjaxResponse array{
62+
* message : string,
63+
* status : int,
64+
* success : bool,
65+
* value : null|float|int|string,
66+
* }
67+
*/
68+
trait MyTrait2
69+
{
70+
/** @return TypeArrayAjaxResponse */
71+
protected function getAjaxResponse(): array
72+
{
73+
return [
74+
"message" => "test",
75+
"status" => 200,
76+
"success" => true,
77+
"value" => 5,
78+
];
79+
}
80+
}
81+
82+
class MyController
83+
{
84+
use MyTrait2;
85+
}
86+
87+
88+
/**
89+
* @phpstan-type X string
90+
*/
91+
class Types {}
92+
93+
/**
94+
* @phpstan-import-type X from Types
95+
*/
96+
trait t {
97+
/** @return X */
98+
public function getX() {
99+
return "123";
100+
}
101+
}
102+
103+
class aClass
104+
{
105+
use t;
106+
}
107+
108+
/**
109+
* @phpstan-import-type X from Types
110+
*/
111+
class Z {
112+
/** @return X */
113+
public function getX() { // works as expected
114+
return "123";
115+
}
116+
}
117+
118+
/**
119+
* @phpstan-type SomePhpstanType array{
120+
* property: mixed
121+
* }
122+
*/
123+
trait TraitWithType
124+
{
125+
/**
126+
* @phpstan-return SomePhpstanType
127+
*/
128+
protected function get(): array
129+
{
130+
return [
131+
'property' => 'something',
132+
];
133+
}
134+
}
135+
136+
/**
137+
* @phpstan-import-type SomePhpstanType from TraitWithType
138+
*/
139+
class ClassWithTraitWithType
140+
{
141+
use TraitWithType;
142+
143+
/**
144+
* @phpstan-return SomePhpstanType
145+
*/
146+
public function SomeMethod(): array
147+
{
148+
return $this->get();
149+
}
150+
}
151+
152+
/**
153+
* @phpstan-type FooJson array{bar: string}
154+
*/
155+
trait Foo {
156+
/**
157+
* @phpstan-return FooJson
158+
*/
159+
public function sayHello(\DateTime $date): array
160+
{
161+
return [
162+
'bar'=> 'baz'
163+
];
164+
}
165+
}
166+
167+
/**
168+
* @phpstan-import-type FooJson from Foo
169+
*/
170+
class HelloWorld
171+
{
172+
use Foo;
173+
}
174+
175+
}

Diff for: tests/PHPStan/Analyser/data/trait-type-alias.php

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace TraitTypeAlias;
4+
5+
use function PHPStan\Testing\assertType;
6+
7+
trait NoAlias
8+
{
9+
10+
/**
11+
* @param Foo $p
12+
*/
13+
public function doFoo($p): void
14+
{
15+
assertType(Foo::class, $p);
16+
}
17+
18+
}
19+
20+
/**
21+
* @phpstan-type Foo array{1}
22+
*/
23+
class UsesNoAlias
24+
{
25+
26+
use NoAlias;
27+
28+
}
29+
30+
/**
31+
* @phpstan-type Foo array{2}
32+
*/
33+
trait WithAlias
34+
{
35+
36+
/**
37+
* @param Foo $p
38+
*/
39+
public function doFoo($p): void
40+
{
41+
assertType('array{2}', $p);
42+
}
43+
44+
}
45+
46+
/**
47+
* @phpstan-type Foo array{1}
48+
*/
49+
class UsesWithAlias
50+
{
51+
52+
use WithAlias;
53+
54+
}

0 commit comments

Comments
 (0)