Skip to content

Commit

Permalink
Fix type aliases in traits
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 7, 2023
1 parent c926144 commit 60021c2
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 6 deletions.
7 changes: 6 additions & 1 deletion src/Analyser/NameScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class NameScope
* @param array<string, string> $constUses alias(string) => fullName(string)
* @param array<string, true> $typeAliasesMap
*/
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 = [])
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)
{
$this->templateTypeMap = $templateTypeMap ?? TemplateTypeMap::createEmpty();
}
Expand Down Expand Up @@ -64,6 +64,11 @@ public function getClassName(): ?string
return $this->className;
}

public function getClassNameForTypeAlias(): ?string
{
return $this->typeAliasClassName ?? $this->className;
}

public function resolveStringName(string $name): string
{
if (strpos($name, '\\') === 0) {
Expand Down
11 changes: 7 additions & 4 deletions src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
}

$traitFound = true;
$typeAliasStack[] = $this->getTypeAliasesMap($node->getDocComment());
$functionStack[] = null;
} else {
if ($node->name === null) {
if (!$node instanceof Node\Stmt\Class_) {
Expand Down Expand Up @@ -304,12 +306,12 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
$phpDocString = GetLastDocComment::forNode($node);
if ($phpDocString !== null) {
$typeMapStack[] = function () use ($namespace, $uses, $className, $functionName, $phpDocString, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap {
$typeMapStack[] = function () use ($namespace, $uses, $className, $lookForTrait, $functionName, $phpDocString, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap {
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;
$currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null;
$typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? [];
$nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, $typeAliasesMap, false, $constUses);
$nameScope = new NameScope($namespace, $uses, $className, $functionName, $currentTypeMap, $typeAliasesMap, false, $constUses, $lookForTrait);
$templateTags = $this->phpDocNodeResolver->resolveTemplateTags($phpDocNode, $nameScope);
$templateTypeScope = $nameScope->getTemplateTypeScope();
if ($templateTypeScope === null) {
Expand Down Expand Up @@ -355,6 +357,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
$typeAliasesMap,
false,
$constUses,
$lookForTrait,
);
}

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

return null;
},
static function (Node $node, $callbackResult) use ($lookForTrait, &$namespace, &$functionStack, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack, &$constUses): void {
if ($node instanceof Node\Stmt\ClassLike && $lookForTrait === null) {
static function (Node $node, $callbackResult) use (&$namespace, &$functionStack, &$classStack, &$typeAliasStack, &$uses, &$typeMapStack, &$constUses): void {
if ($node instanceof Node\Stmt\ClassLike) {
if (count($classStack) === 0) {
throw new ShouldNotHappenException();
}
Expand Down
2 changes: 1 addition & 1 deletion src/Type/UsefulTypeAliasResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
return null;
}

$className = $nameScope->getClassName();
$className = $nameScope->getClassNameForTypeAlias();
if ($className === null) {
return null;
}
Expand Down
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,12 @@ public function testBug9008(): void
$this->assertNoErrors($errors);
}

public function testBug5091(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/bug-5091.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
1 change: 1 addition & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,7 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8956.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-8917.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/ds-copy.php');
yield from $this->gatherAssertTypes(__DIR__ . '/data/trait-type-alias.php');
}

/**
Expand Down
175 changes: 175 additions & 0 deletions tests/PHPStan/Analyser/data/bug-5091.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<?php

namespace Bug5091\Monolog {
/**
* @phpstan-type Record array{message: string}
*/
class Logger {
}
}

namespace Bug5091\Monolog\Handler {
/**
* @phpstan-import-type Record from \Bug5091\Monolog\Logger
*/
class Handler {
use \Bug5091\Monolog\Processor\TraitA;
}
}

namespace Bug5091\Monolog\Processor {
/**
* @phpstan-import-type Record from \Bug5091\Monolog\Logger
*/
trait TraitA {
/**
* @var Record
*/
public $foo;

/**
* @return Record
*/
public function foo() {
return ['message' => ''];
}
}
}

namespace Bug5091 {

/**
* @phpstan-type MyType array{foobar: string}
*/
trait MyTrait
{
/**
* @return array<MyType>
*/
public function MyMethod(): array
{
return [['foobar' => 'foo']];
}
}

class MyClass
{
use MyTrait;
}

/**
* @phpstan-type TypeArrayAjaxResponse array{
* message : string,
* status : int,
* success : bool,
* value : null|float|int|string,
* }
*/
trait MyTrait2
{
/** @return TypeArrayAjaxResponse */
protected function getAjaxResponse(): array
{
return [
"message" => "test",
"status" => 200,
"success" => true,
"value" => 5,
];
}
}

class MyController
{
use MyTrait2;
}


/**
* @phpstan-type X string
*/
class Types {}

/**
* @phpstan-import-type X from Types
*/
trait t {
/** @return X */
public function getX() {
return "123";
}
}

class aClass
{
use t;
}

/**
* @phpstan-import-type X from Types
*/
class Z {
/** @return X */
public function getX() { // works as expected
return "123";
}
}

/**
* @phpstan-type SomePhpstanType array{
* property: mixed
* }
*/
trait TraitWithType
{
/**
* @phpstan-return SomePhpstanType
*/
protected function get(): array
{
return [
'property' => 'something',
];
}
}

/**
* @phpstan-import-type SomePhpstanType from TraitWithType
*/
class ClassWithTraitWithType
{
use TraitWithType;

/**
* @phpstan-return SomePhpstanType
*/
public function SomeMethod(): array
{
return $this->get();
}
}

/**
* @phpstan-type FooJson array{bar: string}
*/
trait Foo {
/**
* @phpstan-return FooJson
*/
public function sayHello(\DateTime $date): array
{
return [
'bar'=> 'baz'
];
}
}

/**
* @phpstan-import-type FooJson from Foo
*/
class HelloWorld
{
use Foo;
}

}
54 changes: 54 additions & 0 deletions tests/PHPStan/Analyser/data/trait-type-alias.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace TraitTypeAlias;

use function PHPStan\Testing\assertType;

trait NoAlias
{

/**
* @param Foo $p
*/
public function doFoo($p): void
{
assertType(Foo::class, $p);
}

}

/**
* @phpstan-type Foo array{1}
*/
class UsesNoAlias
{

use NoAlias;

}

/**
* @phpstan-type Foo array{2}
*/
trait WithAlias
{

/**
* @param Foo $p
*/
public function doFoo($p): void
{
assertType('array{2}', $p);
}

}

/**
* @phpstan-type Foo array{1}
*/
class UsesWithAlias
{

use WithAlias;

}

0 comments on commit 60021c2

Please sign in to comment.