Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
23 changes: 11 additions & 12 deletions src/PseudoTypes/ClassString.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\String_;

use function implode;

/**
* Value Object representing the type 'class-string'.
*
* @psalm-immutable
*/
final class ClassString extends String_ implements PseudoType
{
/** @var Fqsen|null */
private $fqsen;
/** @var Fqsen[] */
private $fqsens;

/**
* Initializes this representation of a class string with the given Fqsen.
*/
public function __construct(?Fqsen $fqsen = null)
public function __construct(Fqsen ...$fqsens)
Comment thread
mspirkov marked this conversation as resolved.
Outdated
{
$this->fqsen = $fqsen;
$this->fqsens = $fqsens;
}

public function underlyingType(): Type
Expand All @@ -42,22 +41,22 @@ public function underlyingType(): Type
}

/**
* Returns the FQSEN associated with this object.
* @return Fqsen[]
*/
public function getFqsen(): ?Fqsen
public function getFqsens(): array
{
return $this->fqsen;
return $this->fqsens;
}

/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->fqsen === null) {
if (!$this->fqsens) {
return 'class-string';
}

return 'class-string<' . (string) $this->fqsen . '>';
return 'class-string<' . implode('|', $this->fqsens) . '>';
}
}
23 changes: 11 additions & 12 deletions src/PseudoTypes/InterfaceString.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,21 @@
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\String_;

use function implode;

/**
* Value Object representing the type `interface-string`.
*
* @psalm-immutable
*/
final class InterfaceString extends String_ implements PseudoType
{
/** @var Fqsen|null */
private $fqsen;
/** @var Fqsen[] */
private $fqsens;

/**
* Initializes this representation of a class string with the given Fqsen.
*/
public function __construct(?Fqsen $fqsen = null)
public function __construct(Fqsen ...$fqsens)
{
$this->fqsen = $fqsen;
$this->fqsens = $fqsens;
}

public function underlyingType(): Type
Expand All @@ -42,22 +41,22 @@ public function underlyingType(): Type
}

/**
* Returns the FQSEN associated with this object.
* @return Fqsen[]
*/
public function getFqsen(): ?Fqsen
public function getFqsens(): array
{
return $this->fqsen;
return $this->fqsens;
}

/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString(): string
{
if ($this->fqsen === null) {
if (!$this->fqsens) {
return 'interface-string';
}

return 'interface-string<' . (string) $this->fqsen . '>';
return 'interface-string<' . implode('|', $this->fqsens) . '>';
}
}
45 changes: 25 additions & 20 deletions src/TypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -404,28 +404,10 @@ private function createFromGeneric(GenericTypeNode $type, Context $context): Typ
return new NonEmptyArray(...$genericTypes);

case 'class-string':
$subType = $this->createType($type->genericTypes[0], $context);
if (!$subType instanceof Object_ || $subType->getFqsen() === null) {
throw new RuntimeException(
$subType . ' is not a class string'
);
}

return new ClassString(
$subType->getFqsen()
);
return new ClassString(...$this->getFqsensByTypeNode($type->genericTypes[0], $context));

case 'interface-string':
$subType = $this->createType($type->genericTypes[0], $context);
if (!$subType instanceof Object_ || $subType->getFqsen() === null) {
throw new RuntimeException(
$subType . ' is not a class string'
);
}

return new InterfaceString(
$subType->getFqsen()
);
return new InterfaceString(...$this->getFqsensByTypeNode($type->genericTypes[0], $context));

case 'list':
return new List_(
Expand Down Expand Up @@ -673,6 +655,29 @@ private function parse(TokenIterator $tokenIterator): TypeNode
return $ast;
}

/**
* @return Fqsen[]
*/
private function getFqsensByTypeNode(TypeNode $node, Context $context): array
{
$nodes = [$node];
if ($node instanceof UnionTypeNode) {
$nodes = $node->types;
}

return array_map(
function (TypeNode $node) use ($context): Fqsen {
$type = $this->createType($node, $context);
if ($type instanceof Object_ === false || $type->getFqsen() === null) {
throw new RuntimeException($type . ' is not a class or interface');
}

return $type->getFqsen();
},
$nodes
);
}

/**
* @param TypeNode[] $nodes
*
Expand Down
10 changes: 7 additions & 3 deletions tests/unit/PseudoTypes/ClassStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@ class ClassStringTest extends TestCase
* @dataProvider provideClassStrings
* @covers ::__toString
*/
public function testClassStringStringifyCorrectly(ClassString $array, string $expectedString): void
public function testClassStringStringifyCorrectly(ClassString $type, string $expectedString): void
{
$this->assertSame($expectedString, (string) $array);
$this->assertSame($expectedString, (string) $type);
}

/**
* @return mixed[]
* @return array<string, array{ClassString, string}>
*/
public function provideClassStrings(): array
{
return [
'generic class string' => [new ClassString(), 'class-string'],
'typed class string' => [new ClassString(new Fqsen('\Foo\Bar')), 'class-string<\Foo\Bar>'],
'more than one class' => [
new ClassString(new Fqsen('\Foo\Bar'), new Fqsen('\Foo\Barrr')),
'class-string<\Foo\Bar|\Foo\Barrr>',
],
];
}
}
10 changes: 7 additions & 3 deletions tests/unit/PseudoTypes/InterfaceStringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@ class InterfaceStringTest extends TestCase
* @dataProvider provideInterfaceStrings
* @covers ::__toString
*/
public function testInterfaceStringStringifyCorrectly(InterfaceString $array, string $expectedString): void
public function testInterfaceStringStringifyCorrectly(InterfaceString $type, string $expectedString): void
{
$this->assertSame($expectedString, (string) $array);
$this->assertSame($expectedString, (string) $type);
}

/**
* @return mixed[]
* @return array<string, array{InterfaceString, string}>
*/
public function provideInterfaceStrings(): array
{
return [
'generic interface string' => [new InterfaceString(), 'interface-string'],
'typed interface string' => [new InterfaceString(new Fqsen('\Foo\Bar')), 'interface-string<\Foo\Bar>'],
'more than one class' => [
new InterfaceString(new Fqsen('\Foo\Bar'), new Fqsen('\Foo\Barrr')),
'interface-string<\Foo\Bar|\Foo\Barrr>',
],
];
}
}
22 changes: 21 additions & 1 deletion tests/unit/TypeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,7 @@ public function provideClassStrings(): array
['class-string<\phpDocumentor\Reflection>', false],
['class-string<\phpDocumentor\Reflection\DocBlock>', false],
['class-string<string>', true],
['class-string<\Foo&\Bar>', true],
];
}

Expand All @@ -856,6 +857,7 @@ public function provideInterfaceStrings(): array
['interface-string<\phpDocumentor\Reflection>', false],
['interface-string<\phpDocumentor\Reflection\DocBlock>', false],
['interface-string<string>', true],
['interface-string<\Foo&\Bar>', true],
];
}

Expand Down Expand Up @@ -1142,16 +1144,34 @@ public function genericsProvider(): array
],
[
'class-string',
new ClassString(null),
new ClassString(),
],
[
'class-string<Foo>',
new ClassString(new Fqsen('\\phpDocumentor\\Foo')),
],
[
'class-string<Foo|Bar>',
new ClassString(
new Fqsen('\\phpDocumentor\\Foo'),
new Fqsen('\\phpDocumentor\\Bar')
),
],
[
'interface-string',
new InterfaceString(),
],
[
'interface-string<Foo>',
new InterfaceString(new Fqsen('\\phpDocumentor\\Foo')),
],
[
'interface-string<Foo|Bar>',
new InterfaceString(
new Fqsen('\\phpDocumentor\\Foo'),
new Fqsen('\\phpDocumentor\\Bar'),
),
],
[
'List<Foo>',
new List_(new Object_(new Fqsen('\\phpDocumentor\\Foo'))),
Expand Down