Skip to content

Commit

Permalink
Helper PHPDoc type: template-type (calling `Type::getTemplateType()…
Browse files Browse the repository at this point in the history
…` method)
  • Loading branch information
ondrejmirtes committed Apr 3, 2023
1 parent b647037 commit b6d0c87
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
use PHPStan\Type\FloatType;
use PHPStan\Type\Generic\GenericClassStringType;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Helper\GetTemplateTypeType;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\IntersectionType;
Expand Down Expand Up @@ -670,6 +671,19 @@ private function resolveGenericTypeNode(GenericTypeNode $typeNode, NameScope $na
if (count($genericTypes) === 1) {
return TypeUtils::toBenevolentUnion($genericTypes[0]);
}
return new ErrorType();
} elseif ($mainTypeName === 'template-type') {
if (count($genericTypes) === 3) {
$result = [];
foreach ($genericTypes[1]->getObjectClassNames() as $ancestorClassName) {
foreach ($genericTypes[2]->getConstantStrings() as $templateTypeName) {
$result[] = new GetTemplateTypeType($genericTypes[0], $ancestorClassName, $templateTypeName->getValue());
}
}

return TypeCombinator::union(...$result);
}

return new ErrorType();
}

Expand Down
83 changes: 83 additions & 0 deletions src/Type/Helper/GetTemplateTypeType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Helper;

use PHPStan\Type\CompoundType;
use PHPStan\Type\Generic\TemplateTypeVariance;
use PHPStan\Type\LateResolvableType;
use PHPStan\Type\Traits\LateResolvableTypeTrait;
use PHPStan\Type\Traits\NonGeneralizableTypeTrait;
use PHPStan\Type\Type;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\VerbosityLevel;
use function sprintf;

/** @api */
final class GetTemplateTypeType implements CompoundType, LateResolvableType
{

use LateResolvableTypeTrait;
use NonGeneralizableTypeTrait;

public function __construct(private Type $type, private string $ancestorClassName, private string $templateTypeName)
{
}

public function getReferencedClasses(): array
{
return $this->type->getReferencedClasses();
}

public function getReferencedTemplateTypes(TemplateTypeVariance $positionVariance): array
{
return $this->type->getReferencedTemplateTypes($positionVariance);
}

public function equals(Type $type): bool
{
return $type instanceof self
&& $this->type->equals($type->type);
}

public function describe(VerbosityLevel $level): string
{
return sprintf('template-type<%s, %s, %s>', $this->type->describe($level), $this->ancestorClassName, $this->templateTypeName);
}

public function isResolvable(): bool
{
return !TypeUtils::containsTemplateType($this->type);
}

protected function getResult(): Type
{
return $this->type->getTemplateType($this->ancestorClassName, $this->templateTypeName);
}

/**
* @param callable(Type): Type $cb
*/
public function traverse(callable $cb): Type
{
$type = $cb($this->type);

if ($this->type === $type) {
return $this;
}

return new self($type, $this->ancestorClassName, $this->templateTypeName);
}

/**
* @param mixed[] $properties
*/
public static function __set_state(array $properties): Type
{
return new self(
$properties['type'],
$properties['ancestorClassName'],
$properties['templateTypeName'],
);
}

}
6 changes: 6 additions & 0 deletions tests/PHPStan/Analyser/AnalyserIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,12 @@ public function testBug5091(): void
$this->assertNoErrors($errors);
}

public function testDiscussion9053(): void
{
$errors = $this->runAnalyse(__DIR__ . '/data/discussion-9053.php');
$this->assertNoErrors($errors);
}

/**
* @param string[]|null $allAnalysedFiles
* @return Error[]
Expand Down
110 changes: 110 additions & 0 deletions tests/PHPStan/Analyser/data/discussion-9053.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace Discussion9053;

use function PHPStan\Testing\assertType;

/**
* @template-covariant TChild of ChildInterface
*/
interface ModelInterface {
/**
* @return TChild[]
*/
public function getChildren(): array;
}

/**
* @implements ModelInterface<Child>
*/
class Model implements ModelInterface
{
/**
* @var Child[]
*/
public array $children;

public function getChildren(): array
{
return $this->children;
}
}

/**
* @template-covariant T of ModelInterface
*/
interface ChildInterface {
/**
* @return T
*/
public function getModel(): ModelInterface;
}


/**
* @implements ChildInterface<Model>
*/
class Child implements ChildInterface
{
public function __construct(private Model $model)
{
}

public function getModel(): Model
{
return $this->model;
}
}

/**
* @template-covariant T of ModelInterface
*/
class Helper
{
/**
* @param T $model
*/
public function __construct(private ModelInterface $model)
{}

/**
* @return template-type<T, ModelInterface, 'TChild'>
*/
public function getFirstChildren(): ChildInterface
{
$firstChildren = $this->model->getChildren()[0] ?? null;

if (!$firstChildren) {
throw new \RuntimeException('No first child found.');
}

return $firstChildren;
}
}

class Other {
/**
* @template TChild of ChildInterface
* @template TModel of ModelInterface<TChild>
* @param Helper<TModel> $helper
* @return TChild
*/
public function getFirstChildren(Helper $helper): ChildInterface {
$child = $helper->getFirstChildren();
assertType('TChild of Discussion9053\ChildInterface (method Discussion9053\Other::getFirstChildren(), argument)', $child);

return $child;
}
}

function (): void {
$model = new Model();
$helper = new Helper($model);
assertType('Discussion9053\Helper<Discussion9053\Model>', $helper);
$child = $helper->getFirstChildren();
assertType('Discussion9053\Child', $child);

$other = new Other();
$child2 = $other->getFirstChildren($helper);
assertType('Discussion9053\Child', $child2);
};

0 comments on commit b6d0c87

Please sign in to comment.