Skip to content

Commit 55a06b0

Browse files
committed
Add support of intersection for return type and dnf
1 parent 9f47e7e commit 55a06b0

File tree

16 files changed

+749
-75
lines changed

16 files changed

+749
-75
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;

spec/Prophecy/Doubler/Generator/Node/ClassNodeSpec.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ function it_can_has_methods(MethodNode $method1, MethodNode $method2)
7676
$this->addMethod($method1);
7777
$this->addMethod($method2);
7878

79-
$this->getMethods()->shouldReturn(array(
79+
$this->getMethods()->shouldReturn([
8080
'__construct' => $method1,
8181
'getName' => $method2,
82-
));
82+
]);
8383
}
8484

8585
function its_hasMethod_returns_true_if_method_exists(MethodNode $method)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace spec\Prophecy\Doubler\Generator\Node\Type;
4+
5+
use PhpSpec\ObjectBehavior;
6+
use Prophecy\Doubler\Generator\Node\Type\SimpleType;
7+
use Prophecy\Doubler\Generator\Node\Type\TypeInterface;
8+
use Prophecy\Doubler\Generator\Node\Type\UnionType;
9+
use Prophecy\Exception\Doubler\DoubleException;
10+
11+
class IntersectionTypeSpec extends ObjectBehavior
12+
{
13+
function let(): void
14+
{
15+
$this->beConstructedWith([
16+
new SimpleType('Foo'),
17+
new SimpleType('Bar'),
18+
]);
19+
}
20+
21+
function it_should_implement_type_union(): void
22+
{
23+
$this->shouldImplement(TypeInterface::class);
24+
}
25+
26+
function it_should_throw_double_exception_for_builtin_types()
27+
{
28+
$this->beConstructedWith([
29+
new SimpleType('string'),
30+
new SimpleType('Foo'),
31+
]);
32+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
33+
}
34+
35+
function it_should_throw_double_exception_if_less_than_2_types_provided()
36+
{
37+
$this->beConstructedWith([
38+
new SimpleType('Bar'),
39+
]);
40+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
41+
}
42+
43+
function it_should_throw_double_exception_if_union_type_given(): void
44+
{
45+
$this->beConstructedWith([
46+
new SimpleType('Bar'),
47+
new UnionType([new SimpleType('Foo'), new SimpleType('Baz')]),
48+
]);
49+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
50+
}
51+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace spec\Prophecy\Doubler\Generator\Node\Type;
4+
5+
use PhpSpec\ObjectBehavior;
6+
use Prophecy\Doubler\Generator\Node\Type\TypeInterface;
7+
8+
class SimpleTypeSpec extends ObjectBehavior
9+
{
10+
function let(): void
11+
{
12+
$this->beConstructedWith('string');
13+
}
14+
15+
function it_implements_type_interface(): void
16+
{
17+
$this->shouldImplement(TypeInterface::class);
18+
}
19+
20+
function it_is_stringable(): void
21+
{
22+
$this->beConstructedWith('int');
23+
$this->getType()->shouldReturn('int');
24+
$this->__toString()->shouldReturn('int');
25+
}
26+
27+
function it_prefix_namespace_with_antislash(): void
28+
{
29+
$this->beConstructedWith('Prophecy\\Doubler\\Generator\\Node\\Type\\SimpleType');
30+
$this->getType()->shouldReturn('\\Prophecy\\Doubler\\Generator\\Node\\Type\\SimpleType');
31+
$this->isBuiltin()->shouldReturn(false);
32+
}
33+
34+
function it_resolves_builtin_aliases(): void
35+
{
36+
$this->beConstructedWith('double');
37+
$this->getType()->shouldReturn('float');
38+
$this->isBuiltin()->shouldReturn(true);
39+
}
40+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace spec\Prophecy\Doubler\Generator\Node\Type;
4+
5+
use PhpSpec\ObjectBehavior;
6+
use Prophecy\Doubler\Generator\Node\Type\IntersectionType;
7+
use Prophecy\Doubler\Generator\Node\Type\SimpleType;
8+
use Prophecy\Doubler\Generator\Node\Type\TypeInterface;
9+
use Prophecy\Doubler\Generator\Node\Type\UnionType;
10+
use Prophecy\Exception\Doubler\DoubleException;
11+
12+
class UnionTypeSpec extends ObjectBehavior
13+
{
14+
function let(): void
15+
{
16+
$this->beConstructedWith([
17+
new SimpleType('int'),
18+
new SimpleType('string'),
19+
]);
20+
}
21+
function it_implements_type_interface(): void
22+
{
23+
$this->shouldImplement(TypeInterface::class);
24+
}
25+
26+
function it_throws_double_exception_when_union_type_given(): void
27+
{
28+
$this->beConstructedWith([
29+
new UnionType([new SimpleType('int'), new SimpleType('string')]),
30+
new SimpleType('bool'),
31+
]);
32+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
33+
}
34+
35+
function it_throws_double_exception_when_types_duplicated(): void
36+
{
37+
$this->beConstructedWith([new SimpleType('string'), new SimpleType('string')]);
38+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
39+
}
40+
41+
function it_throws_double_exception_when_union_with_void(): void
42+
{
43+
$this->beConstructedWith([new SimpleType('void'), new SimpleType('string')]);
44+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
45+
}
46+
47+
function it_throws_double_exception_when_union_with_never(): void
48+
{
49+
$this->beConstructedWith([new SimpleType('never'), new SimpleType('string')]);
50+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
51+
}
52+
53+
function it_throws_double_exception_when_union_with_mixed(): void
54+
{
55+
$this->beConstructedWith([new SimpleType('mixed'), new SimpleType('string')]);
56+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
57+
}
58+
59+
function it_throws_double_exception_when_union_with_only_one_type(): void
60+
{
61+
$this->beConstructedWith([new SimpleType('string')]);
62+
$this->shouldThrow(DoubleException::class)->duringInstantiation();
63+
}
64+
65+
function it_return_array_of_its_types(): void
66+
{
67+
$this->getTypes()->shouldBeLike([
68+
new SimpleType('int'),
69+
new SimpleType('string'),
70+
]);
71+
}
72+
73+
function it_should_accept_simple_type_and_intersection()
74+
{
75+
$type1 = new SimpleType('string');
76+
$type2 = new IntersectionType([new SimpleType('A'), new SimpleType('B')]);
77+
$this->beConstructedWith([$type1, $type2]);
78+
79+
$this->has($type1)->shouldBe(true);
80+
$this->has($type2)->shouldBe(true);
81+
}
82+
}

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
/**

0 commit comments

Comments
 (0)