Skip to content

Commit e0672c9

Browse files
committed
Add support of intersection for return type
1 parent ebb7746 commit e0672c9

File tree

5 files changed

+97
-12
lines changed

5 files changed

+97
-12
lines changed

spec/Prophecy/Doubler/Generator/ClassCodeGeneratorSpec.php

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
use Prophecy\Doubler\Generator\Node\ClassNode;
1111
use Prophecy\Doubler\Generator\Node\MethodNode;
1212
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
13+
use Prophecy\Doubler\Generator\Node\Type\IntersectionType;
14+
use Prophecy\Doubler\Generator\Node\Type\SimpleType;
15+
use Prophecy\Doubler\Generator\Node\Type\UnionType;
1316

1417
class ClassCodeGeneratorSpec extends ObjectBehavior
1518
{
@@ -115,10 +118,10 @@ class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generat
115118
public $name;
116119
private $email;
117120
118-
public static function getName(array $fullname, \ReflectionClass $class, object $instance): ?string {
121+
public static function getName(array $fullname, \ReflectionClass $class, object $instance): string|null {
119122
return $this->name;
120123
}
121-
protected function getEmail(?string $default = '[email protected]') {
124+
protected function getEmail(string|null $default = '[email protected]') {
122125
return $this->email;
123126
}
124127
public function &getRefValue( $refValue): string {
@@ -271,7 +274,7 @@ function it_overrides_properly_methods_with_args_passed_by_reference(
271274
namespace {
272275
class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generator\MirroredInterface {
273276
274-
public function getName(?array &$fullname = NULL) {
277+
public function getName(array|null &$fullname = NULL) {
275278
return $this->name;
276279
}
277280
@@ -310,6 +313,47 @@ public function foo(): int|string|null {
310313
311314
}
312315
316+
}
317+
}
318+
PHP;
319+
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
320+
321+
$code->shouldBe($expected);
322+
}
323+
324+
function it_generates_proper_code_for_intersection_return_types(
325+
ClassNode $class,
326+
MethodNode $method
327+
) {
328+
$class->getParentClass()->willReturn('stdClass');
329+
$class->getInterfaces()->willReturn([]);
330+
$class->getProperties()->willReturn([]);
331+
$class->getMethods()->willReturn(array($method));
332+
$class->isReadOnly()->willReturn(false);
333+
334+
$method->getName()->willReturn('foo');
335+
$method->getVisibility()->willReturn('public');
336+
$method->isStatic()->willReturn(false);
337+
$method->getArguments()->willReturn([]);
338+
$method->getReturnTypeNode()->willReturn(new ReturnTypeNode(
339+
new UnionType([
340+
new IntersectionType([new SimpleType('Foo'), new SimpleType('Bar')]),
341+
new SimpleType('string')
342+
])
343+
));
344+
$method->returnsReference()->willReturn(false);
345+
$method->getCode()->willReturn('');
346+
347+
$code = $this->generate('CustomClass', $class);
348+
349+
$expected = <<<'PHP'
350+
namespace {
351+
class CustomClass extends \stdClass implements {
352+
353+
public function foo(): \Foo&\Bar|string {
354+
355+
}
356+
313357
}
314358
}
315359
PHP;

src/Prophecy/Doubler/Generator/ClassCodeGenerator.php

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
namespace Prophecy\Doubler\Generator;
1313

1414
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
15+
use Prophecy\Doubler\Generator\Node\Type\IntersectionType;
16+
use Prophecy\Doubler\Generator\Node\Type\SimpleType;
17+
use Prophecy\Doubler\Generator\Node\Type\TypeInterface;
18+
use Prophecy\Doubler\Generator\Node\Type\UnionType;
1519
use Prophecy\Doubler\Generator\Node\TypeNodeAbstract;
20+
use Prophecy\Exception\Doubler\ClassCreatorException;
1621

1722
/**
1823
* Class code creator.
@@ -78,16 +83,36 @@ private function generateMethod(Node\MethodNode $method): string
7883

7984
private function generateTypes(TypeNodeAbstract $typeNode): string
8085
{
81-
if (!$typeNode->getTypes()) {
86+
if ($typeNode->getType() === null) {
8287
return '';
8388
}
8489

85-
// When we require PHP 8 we can stop generating ?foo nullables and remove this first block
86-
if ($typeNode->canUseNullShorthand()) {
87-
return sprintf('?%s', $typeNode->getNonNullTypes()[0]);
88-
} else {
89-
return join('|', $typeNode->getTypes());
90+
$generatedType = $this->generateSubType($typeNode->getType());
91+
92+
return $generatedType;
93+
}
94+
95+
private function generateSubType(TypeInterface $type): string
96+
{
97+
if ($type instanceof SimpleType) {
98+
return $type->getType();
9099
}
100+
101+
if ($type instanceof UnionType) {
102+
return join('|', array_map(
103+
fn (TypeInterface $type) => $this->generateSubType($type),
104+
$type->getTypes()
105+
));
106+
}
107+
108+
if ($type instanceof IntersectionType) {
109+
return join('&', array_map(
110+
fn(SimpleType $type) => $type->getType(),
111+
$type->getTypes()
112+
));
113+
}
114+
115+
throw new ClassCreatorException(sprintf('Type "%s" is not supported.', get_class($type)));
91116
}
92117

93118
/**

src/Prophecy/Doubler/Generator/Node/MethodNode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public function getReturnTypeNode(): ReturnTypeNode
207207
*/
208208
public function hasNullableReturnType()
209209
{
210-
return $this->returnTypeNode->canUseNullShorthand();
210+
return $this->returnTypeNode->nullable();
211211
}
212212

213213
/**

src/Prophecy/Doubler/Generator/Node/TypeNodeAbstract.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public function __construct(string|TypeInterface ...$types)
5252
}
5353
}
5454

55+
/**
56+
* @deprecated use nullable() instead
57+
*/
5558
public function canUseNullShorthand(): bool
5659
{
5760
if ($this->type instanceof UnionType) {
@@ -61,6 +64,19 @@ public function canUseNullShorthand(): bool
6164
return false;
6265
}
6366

67+
public function nullable()
68+
{
69+
if ($this->type instanceof UnionType) {
70+
return $this->type->has(new SimpleType('null'));
71+
}
72+
73+
if ($this->type instanceof SimpleType && $this->type->getType() === 'null') {
74+
return true;
75+
}
76+
77+
return false;
78+
}
79+
6480
/**
6581
* @return list<string>
6682
* @deprecated use getType() instead
@@ -86,7 +102,7 @@ public function getTypes(): array
86102
return array_values($types);
87103
}
88104

89-
public function getType(): TypeInterface
105+
public function getType(): ?TypeInterface
90106
{
91107
return $this->type;
92108
}

tests/Doubler/Generator/ClassMirrorTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ public function it_can_not_double_dnf_intersection_argument_types()
744744
* @todo: remove this test and test it works!
745745
* test
746746
*/
747-
public function it_can_not_double_dnf_intersection_return_types()
747+
public function it_can_double_dnf_intersection_return_types()
748748
{
749749
if (PHP_VERSION_ID < 80200) {
750750
$this->markTestSkipped('DNF intersection types are not supported in this PHP version');

0 commit comments

Comments
 (0)