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

[1.x] Adds support for first class callable syntax #33

Merged
merged 1 commit into from
Jan 27, 2022
Merged
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 src/Support/ReflectionClosure.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ public function getCode()
$tokens = $this->getTokens();
$state = $lastState = 'start';
$inside_structure = false;
$isFirstClassCallable = false;
$isShortClosure = false;

$inside_structure_mark = 0;
$open = 0;
$code = '';
Expand All @@ -130,11 +132,15 @@ public function getCode()
case 'start':
if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
$code .= $token[1];

$state = $token[0] === T_FUNCTION ? 'function' : 'static';
} elseif ($token[0] === T_FN) {
$isShortClosure = true;
$code .= $token[1];
$state = 'closure_args';
} elseif ($token[0] === T_PUBLIC || $token[0] === T_PROTECTED || $token[0] === T_PRIVATE) {
$code = '';
$isFirstClassCallable = true;
}
break;
case 'static':
Expand All @@ -155,6 +161,11 @@ public function getCode()
case 'function':
switch ($token[0]) {
case T_STRING:
if ($isFirstClassCallable) {
$state = 'closure_args';
break;
}

$code = '';
$state = 'named_function';
break;
Expand Down
133 changes: 129 additions & 4 deletions tests/ReflectionClosurePhp81Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,21 +107,21 @@ enum ScopedBackedEnum: string {

test('readonly properties', function () {
$f = function () {
$controller = new SerializerPhp81Controller();
$controller = new ReflectionClosurePhp81Controller();

$controller->service = 'foo';
};

$e = 'function () {
$controller = new \SerializerPhp81Controller();
$controller = new \ReflectionClosurePhp81Controller();
$controller->service = \'foo\';
}';

expect($f)->toBeCode($e);
});

test('first-class callable', function () {
test('first-class callable with closures', function () {
$f = function ($a) {
$f = fn ($b) => $a + $b + 1;

Expand All @@ -137,6 +137,77 @@ enum ScopedBackedEnum: string {
expect($f)->toBeCode($e);
});

test('first-class callable with methods', function () {
$f = (new ReflectionClosurePhp81Controller())->publicGetter(...);

$e = 'function ()
{
return $this->privateGetter();
}';

expect($f)->toBeCode($e);

$f = (new ReflectionClosurePhp81Controller())->publicGetterResolver(...);

$e = 'function ()
{
return $this->privateGetterResolver(...);
}';

expect($f)->toBeCode($e);
});

test('first-class callable with static methods', function () {
$f = ReflectionClosurePhp81Controller::publicStaticGetter(...);

$e = 'static function ()
{
return static::privateStaticGetter();
}';

expect($f)->toBeCode($e);

$f = ReflectionClosurePhp81Controller::publicStaticGetterResolver(...);

$e = 'static function ()
{
return static::privateStaticGetterResolver(...);
}';

expect($f)->toBeCode($e);
});

test('first-class callable final method', function () {
$f = (new ReflectionClosurePhp81Controller())->finalPublicGetterResolver(...);

$e = 'function ()
{
return $this->privateGetterResolver(...);
}';

expect($f)->toBeCode($e);

$f = ReflectionClosurePhp81Controller::finalPublicStaticGetterResolver(...);

$e = 'static function ()
{
return static::privateStaticGetterResolver(...);
}';

expect($f)->toBeCode($e);
});

test('first-class callable self return type', function () {
$f = (new ReflectionClosurePhp81Controller())->getSelf(...);

$e = 'function (self $instance): self
{
return $instance;
}';

expect($f)->toBeCode($e);
});

test('intersection types', function () {
$f = function (ClassAlias&Forest $service): ClassAlias&Forest {
return $service;
Expand Down Expand Up @@ -196,5 +267,59 @@ public function __construct(
) {
// ..
}
}

public function publicGetter()
{
return $this->privateGetter();
}

private function privateGetter()
{
return $this->service;
}

public static function publicStaticGetter()
{
return static::privateStaticGetter();
}

public static function privateStaticGetter()
{
return (new ReflectionClosurePhp81Controller())->service;
}

public function publicGetterResolver()
{
return $this->privateGetterResolver(...);
}

private function privateGetterResolver()
{
return fn () => $this->service;
}

public static function publicStaticGetterResolver()
{
return static::privateStaticGetterResolver(...);
}

public static function privateStaticGetterResolver()
{
return fn () => (new ReflectionClosurePhp81Controller())->service;
}

final public function finalPublicGetterResolver()
{
return $this->privateGetterResolver(...);
}

final public static function finalPublicStaticGetterResolver()
{
return static::privateStaticGetterResolver(...);
}

public function getSelf(self $instance): self
{
return $instance;
}
}
109 changes: 108 additions & 1 deletion tests/SerializerPhp81Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ enum SerializerScopedBackedEnum: string {
});
})->with('serializers');

test('first-class callable', function () {
test('first-class callable with closures', function () {
$f = function ($value) {
return $value;
};
Expand All @@ -174,6 +174,58 @@ enum SerializerScopedBackedEnum: string {
expect($f(1)(2))->toBe(4);
})->with('serializers');

test('first-class callable with methods', function () {
$f = (new SerializerPhp81Controller())->publicGetter(...);

$f = s($f);

expect($f())->toBeInstanceOf(SerializerPhp81Service::class);

$f = (new SerializerPhp81Controller())->publicGetterResolver(...);

$f = s($f);

expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);
})->with('serializers');

test('first-class callable with static methods', function () {
$f = SerializerPhp81Controller::publicStaticGetter(...);

$f = s($f);

expect($f())->toBeInstanceOf(SerializerPhp81Service::class);

$f = SerializerPhp81Controller::publicStaticGetterResolver(...);

$f = s($f);

expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);
})->with('serializers');

test('first-class callable final method', function () {
$f = (new SerializerPhp81Controller())->finalPublicGetterResolver(...);

$f = s($f);

expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);

$f = SerializerPhp81Controller::finalPublicStaticGetterResolver(...);

$f = s($f);

expect($f()()())->toBeInstanceOf(SerializerPhp81Service::class);
})->with('serializers');

test('first-class callable self return type', function () {
$f = (new SerializerPhp81Controller())->getSelf(...);

$f = s($f);

$controller = new SerializerPhp81Controller();

expect($f($controller))->toBeInstanceOf(SerializerPhp81Controller::class);
})->with('serializers');

test('intersection types', function () {
$f = function (SerializerPhp81HasName&SerializerPhp81HasId $service): SerializerPhp81HasName&SerializerPhp81HasId {
return $service;
Expand Down Expand Up @@ -243,5 +295,60 @@ public function __construct(
) {
// ..
}

public function publicGetter()
{
return $this->privateGetter();
}

private function privateGetter()
{
return $this->service;
}

public static function publicStaticGetter()
{
return static::privateStaticGetter();
}

public static function privateStaticGetter()
{
return (new SerializerPhp81Controller())->service;
}

public function publicGetterResolver()
{
return $this->privateGetterResolver(...);
}

private function privateGetterResolver()
{
return fn () => $this->service;
}

public static function publicStaticGetterResolver()
{
return static::privateStaticGetterResolver(...);
}

public static function privateStaticGetterResolver()
{
return fn () => (new SerializerPhp81Controller())->service;
}

final public function finalPublicGetterResolver()
{
return $this->privateGetterResolver(...);
}

final public static function finalPublicStaticGetterResolver()
{
return static::privateStaticGetterResolver(...);
}

public function getSelf(self $instance): self
{
return $instance;
}
}