Skip to content

Commit 222e40d

Browse files
authored
Merge pull request #637 from Nek-/intersection-types
Intersection types
2 parents 605d608 + d6d9103 commit 222e40d

29 files changed

+1048
-181
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"phpdocumentor/reflection-docblock": "^5.2",
2323
"sebastian/comparator": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
2424
"doctrine/instantiator": "^1.2 || ^2.0",
25-
"sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
25+
"sebastian/recursion-context": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0",
26+
"symfony/deprecation-contracts": "^2.5 || ^3.1"
2627
},
2728

2829
"require-dev": {

phpstan-baseline.neon

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ parameters:
1212
count: 1
1313
path: src/Prophecy/Doubler/CachedDoubler.php
1414

15+
-
16+
message: '#^Parameter \#1 \$types of class Prophecy\\Doubler\\Generator\\Node\\Type\\UnionType constructor expects list\<Prophecy\\Doubler\\Generator\\Node\\Type\\IntersectionType\|Prophecy\\Doubler\\Generator\\Node\\Type\\SimpleType\>, array\{Prophecy\\Doubler\\Generator\\Node\\Type\\BuiltinType, Prophecy\\Doubler\\Generator\\Node\\Type\\TypeInterface\} given\.$#'
17+
identifier: argument.type
18+
count: 1
19+
path: src/Prophecy/Doubler/ClassPatch/DisableConstructorPatch.php
20+
1521
-
1622
message: '#^Method Prophecy\\Doubler\\Doubler\:\:createDoubleClass\(\) should return class\-string\<Prophecy\\Doubler\\DoubleInterface&T of object\> but returns class\-string\.$#'
1723
identifier: return.type
@@ -30,12 +36,6 @@ parameters:
3036
count: 1
3137
path: src/Prophecy/Doubler/Generator/ClassMirror.php
3238

33-
-
34-
message: '#^Parameter \#1 \$callback of function array_map expects \(callable\(ReflectionIntersectionType\|ReflectionNamedType\|string\)\: mixed\)\|null, Closure\(string\)\: string given\.$#'
35-
identifier: argument.type
36-
count: 1
37-
path: src/Prophecy/Doubler/Generator/ClassMirror.php
38-
3939
-
4040
message: '#^PHPDoc tag @var with type static\(Prophecy\\Doubler\\LazyDouble\<U of object\>\) is not subtype of native type \$this\(Prophecy\\Doubler\\LazyDouble\<T of object\>\)\.$#'
4141
identifier: varTag.nativeType

spec/Prophecy/Doubler/ClassPatch/DisableConstructorPatchSpec.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
use Prophecy\Doubler\Generator\Node\ArgumentTypeNode;
99
use Prophecy\Doubler\Generator\Node\ClassNode;
1010
use Prophecy\Doubler\Generator\Node\MethodNode;
11+
use Prophecy\Doubler\Generator\Node\Type\BuiltinType;
12+
use Prophecy\Doubler\Generator\Node\Type\UnionType;
1113

1214
class DisableConstructorPatchSpec extends ObjectBehavior
1315
{
@@ -30,20 +32,26 @@ function it_makes_all_constructor_arguments_optional(
3032
ClassNode $class,
3133
MethodNode $method,
3234
ArgumentNode $arg1,
33-
ArgumentNode $arg2
35+
ArgumentNode $arg2,
36+
ArgumentNode $arg3
3437
) {
35-
$arg1->getTypeNode()->willReturn(new ArgumentTypeNode('string', 'null'));
36-
$arg2->getTypeNode()->willReturn(new ArgumentTypeNode('mixed'));
38+
$arg1->getTypeNode()->willReturn(new ArgumentTypeNode(new UnionType([
39+
new BuiltinType('string'),
40+
new BuiltinType('null'),
41+
])));
42+
$arg2->getTypeNode()->willReturn(new ArgumentTypeNode(new BuiltinType('mixed')));
43+
$arg3->getTypeNode()->willReturn(new ArgumentTypeNode(new BuiltinType('string')));
3744

3845
$class->isExtendable('__construct')->willReturn(true);
3946
$class->hasMethod('__construct')->willReturn(true);
4047
$class->getMethod('__construct')->willReturn($method);
41-
$method->getArguments()->willReturn(array($arg1, $arg2));
48+
$method->getArguments()->willReturn(array($arg1, $arg2, $arg3));
4249

4350
$arg1->setDefault(null)->shouldBeCalled();
4451
$arg2->setDefault(null)->shouldBeCalled();
52+
$arg3->setDefault(null)->shouldBeCalled();
4553

46-
$arg1->setTypeNode(new ArgumentTypeNode('null', 'string'))->shouldBeCalled();
54+
$arg3->setTypeNode(new ArgumentTypeNode(new UnionType([new BuiltinType('null'), new BuiltinType('string')])))->shouldBeCalled();
4755

4856
$method->setCode(Argument::type('string'))->shouldBeCalled();
4957

spec/Prophecy/Doubler/ClassPatch/ProphecySubjectPatchSpec.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Prophecy\Doubler\Generator\Node\ClassNode;
88
use Prophecy\Doubler\Generator\Node\MethodNode;
99
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
10+
use Prophecy\Doubler\Generator\Node\Type\BuiltinType;
1011

1112
class ProphecySubjectPatchSpec extends ObjectBehavior
1213
{
@@ -58,10 +59,10 @@ function it_forces_all_class_methods_except_constructor_to_proxy_calls_into_prop
5859
$method3->getName()->willReturn('method3');
5960
$method4->getName()->willReturn('method4');
6061

61-
$method1->getReturnTypeNode()->willReturn(new ReturnTypeNode('int'));
62-
$method2->getReturnTypeNode()->willReturn(new ReturnTypeNode('int'));
63-
$method3->getReturnTypeNode()->willReturn(new ReturnTypeNode('void'));
64-
$method4->getReturnTypeNode()->willReturn(new ReturnTypeNode('never'));
62+
$method1->getReturnTypeNode()->willReturn(new ReturnTypeNode(new BuiltinType('int')));
63+
$method2->getReturnTypeNode()->willReturn(new ReturnTypeNode(new BuiltinType('int')));
64+
$method3->getReturnTypeNode()->willReturn(new ReturnTypeNode(new BuiltinType('void')));
65+
$method4->getReturnTypeNode()->willReturn(new ReturnTypeNode(new BuiltinType('never')));
6566

6667
$node->getMethods()->willReturn(array(
6768
'method1' => $method1,

spec/Prophecy/Doubler/Generator/ClassCodeGeneratorSpec.php

Lines changed: 118 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
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\BuiltinType;
14+
use Prophecy\Doubler\Generator\Node\Type\IntersectionType;
15+
use Prophecy\Doubler\Generator\Node\Type\ObjectType;
16+
use Prophecy\Doubler\Generator\Node\Type\SimpleType;
17+
use Prophecy\Doubler\Generator\Node\Type\UnionType;
1318

1419
class ClassCodeGeneratorSpec extends ObjectBehavior
1520
{
@@ -39,7 +44,10 @@ function it_generates_proper_php_code_for_specific_ClassNode(
3944
$method1->returnsReference()->willReturn(false);
4045
$method1->isStatic()->willReturn(true);
4146
$method1->getArguments()->willReturn(array($argument11, $argument12, $argument13));
42-
$method1->getReturnTypeNode()->willReturn(new ReturnTypeNode('string', 'null'));
47+
$method1->getReturnTypeNode()->willReturn(new ReturnTypeNode(new UnionType([
48+
new BuiltinType('string'),
49+
new BuiltinType('null'),
50+
])));
4351
$method1->getCode()->willReturn('return $this->name;');
4452

4553
$method2->getName()->willReturn('getEmail');
@@ -55,49 +63,52 @@ function it_generates_proper_php_code_for_specific_ClassNode(
5563
$method3->returnsReference()->willReturn(true);
5664
$method3->isStatic()->willReturn(false);
5765
$method3->getArguments()->willReturn(array($argument31));
58-
$method3->getReturnTypeNode()->willReturn(new ReturnTypeNode('string'));
66+
$method3->getReturnTypeNode()->willReturn(new ReturnTypeNode(new BuiltinType('string')));
5967
$method3->getCode()->willReturn('return $this->refValue;');
6068

6169
$method4->getName()->willReturn('doSomething');
6270
$method4->getVisibility()->willReturn('public');
6371
$method4->returnsReference()->willReturn(false);
6472
$method4->isStatic()->willReturn(false);
6573
$method4->getArguments()->willReturn(array());
66-
$method4->getReturnTypeNode()->willReturn(new ReturnTypeNode('void'));
74+
$method4->getReturnTypeNode()->willReturn(new ReturnTypeNode(new BuiltinType('void')));
6775
$method4->getCode()->willReturn('return;');
6876

6977
$method5->getName()->willReturn('returnObject');
7078
$method5->getVisibility()->willReturn('public');
7179
$method5->returnsReference()->willReturn(false);
7280
$method5->isStatic()->willReturn(false);
7381
$method5->getArguments()->willReturn(array());
74-
$method5->getReturnTypeNode()->willReturn(new ReturnTypeNode('object'));
82+
$method5->getReturnTypeNode()->willReturn(new ReturnTypeNode(new BuiltinType('object')));
7583
$method5->getCode()->willReturn('return;');
7684

7785
$argument11->getName()->willReturn('fullname');
7886
$argument11->isOptional()->willReturn(false);
7987
$argument11->isPassedByReference()->willReturn(false);
8088
$argument11->isVariadic()->willReturn(false);
81-
$argument11->getTypeNode()->willReturn(new ArgumentTypeNode('array'));
89+
$argument11->getTypeNode()->willReturn(new ArgumentTypeNode(new BuiltinType('array')));
8290

8391
$argument12->getName()->willReturn('class');
8492
$argument12->isOptional()->willReturn(false);
8593
$argument12->isPassedByReference()->willReturn(false);
8694
$argument12->isVariadic()->willReturn(false);
87-
$argument12->getTypeNode()->willReturn(new ArgumentTypeNode('ReflectionClass'));
95+
$argument12->getTypeNode()->willReturn(new ArgumentTypeNode(new ObjectType('ReflectionClass')));
8896

8997
$argument13->getName()->willReturn('instance');
9098
$argument13->isOptional()->willReturn(false);
9199
$argument13->isPassedByReference()->willReturn(false);
92100
$argument13->isVariadic()->willReturn(false);
93-
$argument13->getTypeNode()->willReturn(new ArgumentTypeNode('object'));
101+
$argument13->getTypeNode()->willReturn(new ArgumentTypeNode(new BuiltinType('object')));
94102

95103
$argument21->getName()->willReturn('default');
96104
$argument21->isOptional()->willReturn(true);
97105
$argument21->getDefault()->willReturn('ever.zet@gmail.com');
98106
$argument21->isPassedByReference()->willReturn(false);
99107
$argument21->isVariadic()->willReturn(false);
100-
$argument21->getTypeNode()->willReturn(new ArgumentTypeNode('string', 'null'));
108+
$argument21->getTypeNode()->willReturn(new ArgumentTypeNode(new UnionType([
109+
new BuiltinType('string'),
110+
new BuiltinType('null'),
111+
])));
101112

102113
$argument31->getName()->willReturn('refValue');
103114
$argument31->isOptional()->willReturn(false);
@@ -115,10 +126,10 @@ class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generat
115126
public $name;
116127
private $email;
117128
118-
public static function getName(array $fullname, \ReflectionClass $class, object $instance): ?string {
129+
public static function getName(array $fullname, \ReflectionClass $class, object $instance): string|null {
119130
return $this->name;
120131
}
121-
protected function getEmail(?string $default = 'ever.zet@gmail.com') {
132+
protected function getEmail(string|null $default = 'ever.zet@gmail.com') {
122133
return $this->email;
123134
}
124135
public function &getRefValue( $refValue): string {
@@ -206,13 +217,13 @@ function it_generates_proper_php_code_for_variadics(
206217
$argument3->isOptional()->willReturn(false);
207218
$argument3->isPassedByReference()->willReturn(false);
208219
$argument3->isVariadic()->willReturn(true);
209-
$argument3->getTypeNode()->willReturn(new ArgumentTypeNode('ReflectionClass'));
220+
$argument3->getTypeNode()->willReturn(new ArgumentTypeNode(new ObjectType('ReflectionClass')));
210221

211222
$argument4->getName()->willReturn('args');
212223
$argument4->isOptional()->willReturn(false);
213224
$argument4->isPassedByReference()->willReturn(true);
214225
$argument4->isVariadic()->willReturn(true);
215-
$argument4->getTypeNode()->willReturn(new ArgumentTypeNode('ReflectionClass'));
226+
$argument4->getTypeNode()->willReturn(new ArgumentTypeNode(new ObjectType('ReflectionClass')));
216227

217228

218229
$code = $this->generate('CustomClass', $class);
@@ -264,14 +275,17 @@ function it_overrides_properly_methods_with_args_passed_by_reference(
264275
$argument->getDefault()->willReturn(null);
265276
$argument->isPassedByReference()->willReturn(true);
266277
$argument->isVariadic()->willReturn(false);
267-
$argument->getTypeNode()->willReturn(new ArgumentTypeNode('array', 'null'));
278+
$argument->getTypeNode()->willReturn(new ArgumentTypeNode(new UnionType([
279+
new BuiltinType('array'),
280+
new BuiltinType('null'),
281+
])));
268282

269283
$code = $this->generate('CustomClass', $class);
270284
$expected = <<<'PHP'
271285
namespace {
272286
class CustomClass extends \RuntimeException implements \Prophecy\Doubler\Generator\MirroredInterface {
273287
274-
public function getName(?array &$fullname = NULL) {
288+
public function getName(array|null &$fullname = NULL) {
275289
return $this->name;
276290
}
277291
@@ -296,7 +310,11 @@ function it_generates_proper_code_for_union_return_types(
296310
$method->getVisibility()->willReturn('public');
297311
$method->isStatic()->willReturn(false);
298312
$method->getArguments()->willReturn([]);
299-
$method->getReturnTypeNode()->willReturn(new ReturnTypeNode('int', 'string', 'null'));
313+
$method->getReturnTypeNode()->willReturn(new ReturnTypeNode(new UnionType([
314+
new BuiltinType('int'),
315+
new BuiltinType('string'),
316+
new BuiltinType('null'),
317+
])));
300318
$method->returnsReference()->willReturn(false);
301319
$method->getCode()->willReturn('');
302320

@@ -310,6 +328,86 @@ public function foo(): int|string|null {
310328
311329
}
312330
331+
}
332+
}
333+
PHP;
334+
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
335+
336+
$code->shouldBe($expected);
337+
}
338+
339+
function it_generates_proper_code_for_dnf_types(
340+
ClassNode $class,
341+
MethodNode $method
342+
) {
343+
$class->getParentClass()->willReturn('stdClass');
344+
$class->getInterfaces()->willReturn([]);
345+
$class->getProperties()->willReturn([]);
346+
$class->getMethods()->willReturn(array($method));
347+
$class->isReadOnly()->willReturn(false);
348+
349+
$method->getName()->willReturn('foo');
350+
$method->getVisibility()->willReturn('public');
351+
$method->isStatic()->willReturn(false);
352+
$method->getArguments()->willReturn([]);
353+
$method->getReturnTypeNode()->willReturn(new ReturnTypeNode(
354+
new UnionType([
355+
new IntersectionType([new ObjectType('Foo'), new ObjectType('Bar')]),
356+
new BuiltinType('string'),
357+
])
358+
));
359+
$method->returnsReference()->willReturn(false);
360+
$method->getCode()->willReturn('');
361+
362+
$code = $this->generate('CustomClass', $class);
363+
364+
$expected = <<<'PHP'
365+
namespace {
366+
class CustomClass extends \stdClass implements {
367+
368+
public function foo(): (\Foo&\Bar)|string {
369+
370+
}
371+
372+
}
373+
}
374+
PHP;
375+
$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
376+
377+
$code->shouldBe($expected);
378+
}
379+
380+
381+
function it_generates_proper_code_for_intersection_return_types(
382+
ClassNode $class,
383+
MethodNode $method
384+
) {
385+
$class->getParentClass()->willReturn('stdClass');
386+
$class->getInterfaces()->willReturn([]);
387+
$class->getProperties()->willReturn([]);
388+
$class->getMethods()->willReturn(array($method));
389+
$class->isReadOnly()->willReturn(false);
390+
391+
$method->getName()->willReturn('foo');
392+
$method->getVisibility()->willReturn('public');
393+
$method->isStatic()->willReturn(false);
394+
$method->getArguments()->willReturn([]);
395+
$method->getReturnTypeNode()->willReturn(new ReturnTypeNode(
396+
new IntersectionType([new ObjectType('Foo'), new ObjectType('Bar')])
397+
));
398+
$method->returnsReference()->willReturn(false);
399+
$method->getCode()->willReturn('');
400+
401+
$code = $this->generate('CustomClass', $class);
402+
403+
$expected = <<<'PHP'
404+
namespace {
405+
class CustomClass extends \stdClass implements {
406+
407+
public function foo(): \Foo&\Bar {
408+
409+
}
410+
313411
}
314412
}
315413
PHP;
@@ -337,7 +435,11 @@ function it_generates_proper_code_for_union_argument_types(
337435
$method->returnsReference()->willReturn(false);
338436
$method->getCode()->willReturn('');
339437

340-
$argument->getTypeNode()->willReturn(new ArgumentTypeNode('int', 'string', 'null'));
438+
$argument->getTypeNode()->willReturn(new ArgumentTypeNode(new UnionType([
439+
new BuiltinType('int'),
440+
new BuiltinType('string'),
441+
new BuiltinType('null'),
442+
])));
341443
$argument->getName()->willReturn('arg');
342444
$argument->isPassedByReference()->willReturn(false);
343445
$argument->isVariadic()->willReturn(false);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PhpSpec\ObjectBehavior;
66
use Prophecy\Doubler\Generator\Node\ArgumentTypeNode;
7+
use Prophecy\Doubler\Generator\Node\Type\BuiltinType;
78

89
class ArgumentNodeSpec extends ObjectBehavior
910
{
@@ -94,9 +95,9 @@ function it_has_an_empty_type_by_default()
9495

9596
function it_has_a_mutable_type()
9697
{
97-
$this->setTypeNode(new ArgumentTypeNode('int'));
98+
$this->setTypeNode(new ArgumentTypeNode(new BuiltinType('int')));
9899

99-
$this->getTypeNode()->shouldBeLike(new ArgumentTypeNode('int'));
100+
$this->getTypeNode()->shouldBeLike(new ArgumentTypeNode(new BuiltinType('int')));
100101
}
101102

102103
function it_does_not_have_default_value_by_default()

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)

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use PhpSpec\ObjectBehavior;
66
use Prophecy\Doubler\Generator\Node\ArgumentNode;
77
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
8+
use Prophecy\Doubler\Generator\Node\Type\BuiltinType;
89

910
class MethodNodeSpec extends ObjectBehavior
1011
{
@@ -125,9 +126,9 @@ function it_has_an_empty_return_type_by_default()
125126

126127
function it_can_modify_return_type()
127128
{
128-
$this->setReturnTypeNode(new ReturnTypeNode('array'));
129+
$this->setReturnTypeNode(new ReturnTypeNode(new BuiltinType('array')));
129130

130-
$this->getReturnTypeNode()->shouldBeLike(new ReturnTypeNode('array'));
131+
$this->getReturnTypeNode()->shouldBeLike(new ReturnTypeNode(new BuiltinType('array')));
131132
}
132133

133134
function it_can_modify_return_type_as_strings_using_deprecated_methods()

0 commit comments

Comments
 (0)