Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial idea for Intersection/Union types #569

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions fixtures/VoidReturnType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Fixtures\Prophecy;

class VoidReturnType
{
public function doSomething(): void
{

}
}
2 changes: 1 addition & 1 deletion spec/Prophecy/Doubler/ClassPatch/TraversablePatchSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function it_adds_methods_to_implement_iterator(ClassNode $node)
$node->addMethod(Argument::that(static function ($value) use ($methodName, $returnType) {
return $value instanceof MethodNode
&& $value->getName() === $methodName
&& (\PHP_VERSION_ID < 80100 || $value->getReturnTypeNode()->getTypes() === [$returnType]);
&& (\PHP_VERSION_ID < 80100 || $value->getReturnTypeNode()->getType() === [$returnType]);
}))->shouldBeCalled();
}

Expand Down
45 changes: 32 additions & 13 deletions spec/Prophecy/Doubler/Generator/ClassCodeGeneratorSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use Prophecy\Doubler\Generator\Node\ClassNode;
use Prophecy\Doubler\Generator\Node\MethodNode;
use Prophecy\Doubler\Generator\Node\ReturnTypeNode;
use Prophecy\Doubler\Generator\Node\Type\NamedTypeNode;
use Prophecy\Doubler\Generator\Node\Type\UnionTypeNode;

class ClassCodeGeneratorSpec extends ObjectBehavior
{
Expand Down Expand Up @@ -38,7 +40,9 @@ function it_generates_proper_php_code_for_specific_ClassNode(
$method1->returnsReference()->willReturn(false);
$method1->isStatic()->willReturn(true);
$method1->getArguments()->willReturn(array($argument11, $argument12, $argument13));
$method1->getReturnTypeNode()->willReturn(new ReturnTypeNode('string', 'null'));
$method1->getReturnTypeNode()->willReturn(new ReturnTypeNode(
new NamedTypeNode('string', true, true)
));
$method1->getCode()->willReturn('return $this->name;');

$method2->getName()->willReturn('getEmail');
Expand All @@ -54,50 +58,50 @@ function it_generates_proper_php_code_for_specific_ClassNode(
$method3->returnsReference()->willReturn(true);
$method3->isStatic()->willReturn(false);
$method3->getArguments()->willReturn(array($argument31));
$method3->getReturnTypeNode()->willReturn(new ReturnTypeNode('string'));
$method3->getReturnTypeNode()->willReturn(new ReturnTypeNode(new NamedTypeNode('string', false, true)));
$method3->getCode()->willReturn('return $this->refValue;');

$method4->getName()->willReturn('doSomething');
$method4->getVisibility()->willReturn('public');
$method4->returnsReference()->willReturn(false);
$method4->isStatic()->willReturn(false);
$method4->getArguments()->willReturn(array());
$method4->getReturnTypeNode()->willReturn(new ReturnTypeNode('void'));
$method4->getReturnTypeNode()->willReturn(new ReturnTypeNode(new NamedTypeNode('void', false, true)));
$method4->getCode()->willReturn('return;');

$method5->getName()->willReturn('returnObject');
$method5->getVisibility()->willReturn('public');
$method5->returnsReference()->willReturn(false);
$method5->isStatic()->willReturn(false);
$method5->getArguments()->willReturn(array());
$method5->getReturnTypeNode()->willReturn(new ReturnTypeNode('object'));
$method5->getReturnTypeNode()->willReturn(new ReturnTypeNode(new NamedTypeNode('object', false, true)));
$method5->getCode()->willReturn('return;');

$argument11->getName()->willReturn('fullname');
$argument11->isOptional()->willReturn(true);
$argument11->getDefault()->willReturn(null);
$argument11->isPassedByReference()->willReturn(false);
$argument11->isVariadic()->willReturn(false);
$argument11->getTypeNode()->willReturn(new ArgumentTypeNode('array'));
$argument11->getTypeNode()->willReturn(new ArgumentTypeNode(new NamedTypeNode('array', false, true)));

$argument12->getName()->willReturn('class');
$argument12->isOptional()->willReturn(false);
$argument12->isPassedByReference()->willReturn(false);
$argument12->isVariadic()->willReturn(false);
$argument12->getTypeNode()->willReturn(new ArgumentTypeNode('ReflectionClass'));
$argument12->getTypeNode()->willReturn(new ArgumentTypeNode(new NamedTypeNode('\ReflectionClass', false, false)));

$argument13->getName()->willReturn('instance');
$argument13->isOptional()->willReturn(false);
$argument13->isPassedByReference()->willReturn(false);
$argument13->isVariadic()->willReturn(false);
$argument13->getTypeNode()->willReturn(new ArgumentTypeNode('object'));
$argument13->getTypeNode()->willReturn(new ArgumentTypeNode(new NamedTypeNode('object', false, true)));

$argument21->getName()->willReturn('default');
$argument21->isOptional()->willReturn(true);
$argument21->getDefault()->willReturn('[email protected]');
$argument21->isPassedByReference()->willReturn(false);
$argument21->isVariadic()->willReturn(false);
$argument21->getTypeNode()->willReturn(new ArgumentTypeNode('string', 'null'));
$argument21->getTypeNode()->willReturn(new ArgumentTypeNode(new NamedTypeNode('string', true, true)));

$argument31->getName()->willReturn('refValue');
$argument31->isOptional()->willReturn(false);
Expand Down Expand Up @@ -135,6 +139,7 @@ public function returnObject(): object {
}
PHP;


$expected = strtr($expected, array("\r\n" => "\n", "\r" => "\n"));
$code->shouldBe($expected);
}
Expand Down Expand Up @@ -205,13 +210,13 @@ function it_generates_proper_php_code_for_variadics(
$argument3->isOptional()->willReturn(false);
$argument3->isPassedByReference()->willReturn(false);
$argument3->isVariadic()->willReturn(true);
$argument3->getTypeNode()->willReturn(new ArgumentTypeNode('ReflectionClass'));
$argument3->getTypeNode()->willReturn(new ArgumentTypeNode(new NamedTypeNode('\ReflectionClass', false, false)));

$argument4->getName()->willReturn('args');
$argument4->isOptional()->willReturn(false);
$argument4->isPassedByReference()->willReturn(true);
$argument4->isVariadic()->willReturn(true);
$argument4->getTypeNode()->willReturn(new ArgumentTypeNode('ReflectionClass'));
$argument4->getTypeNode()->willReturn(new ArgumentTypeNode(new NamedTypeNode('\ReflectionClass', false, false)));


$code = $this->generate('CustomClass', $class);
Expand Down Expand Up @@ -262,7 +267,7 @@ function it_overrides_properly_methods_with_args_passed_by_reference(
$argument->getDefault()->willReturn(null);
$argument->isPassedByReference()->willReturn(true);
$argument->isVariadic()->willReturn(false);
$argument->getTypeNode()->willReturn(new ArgumentTypeNode('array'));
$argument->getTypeNode()->willReturn(new ArgumentTypeNode(new NamedTypeNode('array', false, false)));

$code = $this->generate('CustomClass', $class);
$expected =<<<'PHP'
Expand Down Expand Up @@ -295,7 +300,14 @@ function it_generates_proper_code_for_union_return_types
$method->getVisibility()->willReturn('public');
$method->isStatic()->willReturn(false);
$method->getArguments()->willReturn([]);
$method->getReturnTypeNode()->willReturn(new ReturnTypeNode('int', 'string', 'null'));
$method->getReturnTypeNode()->willReturn(new ReturnTypeNode(
new UnionTypeNode(
false,
new NamedTypeNode('int', false, true),
new NamedTypeNode('string', false, true),
new NamedTypeNode('null', false, true)
)
));
$method->returnsReference()->willReturn(false);
$method->getCode()->willReturn('');

Expand Down Expand Up @@ -337,7 +349,14 @@ function it_generates_proper_code_for_union_argument_types
$method->returnsReference()->willReturn(false);
$method->getCode()->willReturn('');

$argument->getTypeNode()->willReturn(new ArgumentTypeNode('int', 'string', 'null'));
$argument->getTypeNode()->willReturn(new ArgumentTypeNode(
new UnionTypeNode(
false,
new NamedTypeNode('int', false, true),
new NamedTypeNode('string', false, true),
new NamedTypeNode('null', false, true)
)
));
$argument->getName()->willReturn('arg');
$argument->isPassedByReference()->willReturn(false);
$argument->isVariadic()->willReturn(false);
Expand Down
84 changes: 47 additions & 37 deletions spec/Prophecy/Doubler/Generator/Node/ArgumentTypeNodeSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,97 +4,106 @@

use PhpSpec\ObjectBehavior;
use Prophecy\Doubler\Generator\Node\ArgumentTypeNode;
use Prophecy\Doubler\Generator\Node\Type\IntersectionTypeNode;
use Prophecy\Doubler\Generator\Node\Type\NamedTypeNode;
use Prophecy\Doubler\Generator\Node\Type\UnionTypeNode;
use Prophecy\Exception\Doubler\DoubleException;

class ArgumentTypeNodeSpec extends ObjectBehavior
{
function it_has_no_types_at_start()
{
$this->getTypes()->shouldReturn([]);
$this->getType()->shouldReturn(null);
}

function it_can_have_a_simple_type()
{
$this->beConstructedWith('int');

$this->getTypes()->shouldReturn(['int']);
}

function it_can_have_multiple_types()
{
$this->beConstructedWith('int', 'string');

$this->getTypes()->shouldReturn(['int', 'string']);
$node = new NamedTypeNode('int', false, true);
$this->beConstructedWith($node);
$this->getType()->shouldReturn($node);
}

function it_will_prefix_fcqns()
function it_can_have_multiple_union_types()
{
$this->beConstructedWith('Foo');
$int = new NamedTypeNode('int', false, true);
$string = new NamedTypeNode('string', false, true);
$union = new UnionTypeNode(false, $int, $string);
$this->beConstructedWith($union);

$this->getTypes()->shouldReturn(['\\Foo']);
$this->getType()->shouldReturn($union);
}

function it_will_not_prefix_fcqns_that_already_have_prefix()
function it_can_have_multiple_intersection_types()
{
$this->beConstructedWith('\\Foo');
$int = new NamedTypeNode('int', false, true);
$string = new NamedTypeNode('string', false, true);
$intersection = new IntersectionTypeNode(false, $int, $string);
$this->beConstructedWith($intersection);

$this->getTypes()->shouldReturn(['\\Foo']);
$this->getType()->shouldReturn($intersection);
}

function it_can_use_shorthand_null_syntax_if_it_has_single_type_plus_null()
function it_can_use_shorthand_null_syntax_if_it_is_named_type_node_and_allows_null()
{
$this->beConstructedWith('int', 'null');
$int = new NamedTypeNode('int', true, true);
$this->beConstructedWith($int);

$this->canUseNullShorthand()->shouldReturn(true);
}

function it_can_not_use_shorthand_null_syntax_if_it_does_not_allow_null()
function it_can_not_use_shorthand_if_its_not_named_type_node()
{
$this->beConstructedWith('int');
$int = new NamedTypeNode('int', false, true);
$string = new NamedTypeNode('string', false, true);
$intersection = new IntersectionTypeNode(false, $int, $string);
$this->beConstructedWith($intersection);

$this->canUseNullShorthand()->shouldReturn(false);
}

function it_can_not_use_shorthand_null_syntax_if_it_has_more_than_one_non_null_type()
function it_can_not_use_shorthand_if_its_named_type_node_but_does_not_allow_null()
{
$this->beConstructedWith('int', 'string', 'null');
$int = new NamedTypeNode('int', false, true);
$this->beConstructedWith($int);

$this->canUseNullShorthand()->shouldReturn(false);
}

function it_can_return_non_null_types()
{
$this->beConstructedWith('int', 'null');

$this->getNonNullTypes()->shouldReturn(['int']);
}

function it_does_not_allow_standalone_null()
{
$this->beConstructedWith('null');
$null = new NamedTypeNode('null', false, true);
$this->beConstructedWith($null);

$this->shouldThrow(DoubleException::class)->duringInstantiation();
}

function it_does_not_allow_union_mixed()
{
$this->beConstructedWith('mixed', 'int');
$void = new NamedTypeNode('mixed', false, true);
$int = new NamedTypeNode('int', false, true);
$union = new UnionTypeNode(false, $void, $int);

$this->beConstructedWith($union);

if (PHP_VERSION_ID >=80000) {
$this->shouldThrow(DoubleException::class)->duringInstantiation();
}
}

function it_does_not_prefix_false()
function it_does_not_prefix_false_in_a_union()
{
$this->beConstructedWith('false', 'array');
$array = new NamedTypeNode('array', false, true);
$false = new NamedTypeNode('false', false, true);
$union = new UnionTypeNode(false, $array, $false);
$this->beConstructedWith($union);

$this->getTypes()->shouldReturn(['false', 'array']);
$this->getType()->getTypes()[0]->getName()->shouldReturn('array');
}

function it_does_not_allow_standalone_false()
{
$this->beConstructedWith('false');
$false = new NamedTypeNode('false', false, true);
$this->beConstructedWith($false);

if (PHP_VERSION_ID >=80000) {
$this->shouldThrow(DoubleException::class)->duringInstantiation();
Expand All @@ -103,7 +112,8 @@ function it_does_not_allow_standalone_false()

function it_does_not_allow_nullable_false()
{
$this->beConstructedWith('null', 'false');
$false = new NamedTypeNode('false', true, true);
$this->beConstructedWith($false);

if (PHP_VERSION_ID >=80000) {
$this->shouldThrow(DoubleException::class)->duringInstantiation();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace spec\Prophecy\Doubler\Generator\Node\NameNormalization;

use PhpSpec\ObjectBehavior;
use Prophecy\Exception\Doubler\DoubleException;

class ArgumentTypeNameNormalizationSpec extends ObjectBehavior
{
function it_has_no_types_at_start()
{
$this->normalize()->shouldReturn([]);
}

function it_can_have_a_simple_type()
{
$this->normalize('int')->shouldReturn(['int']);
}

function it_can_have_multiple_types()
{
$this->normalize('int', 'string')->shouldReturn(['int', 'string']);
}

function it_will_prefix_fcqns()
{
$this->normalize('Foo')->shouldReturn(['\\Foo']);
}

function it_will_not_prefix_fcqns_that_already_have_prefix()
{
$this->beConstructedWith();

$this->normalize('\\Foo')->shouldReturn(['\\Foo']);
}

function it_does_not_prefix_false()
{
$this->beConstructedWith();

$this->normalize('false', 'array')->shouldReturn(['false', 'array']);
}
}
Loading