Skip to content

Commit

Permalink
Fix signatures for functions inside annotations when using PHP 7
Browse files Browse the repository at this point in the history
  • Loading branch information
thekid committed Jun 25, 2023
1 parent 70af1ed commit e70037e
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 5 deletions.
5 changes: 5 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ XP Reflection ChangeLog

## ?.?.? / ????-??-??

* Fixed parameter default values, by-reference and variadic markers,
and parameter and return types being swallowed for functions inside
annotations when using PHP 7
(@thekid)

## 2.13.4 / 2023-06-25

* Fixed parsing global imports and grouped imports containing aliases,
Expand Down
26 changes: 21 additions & 5 deletions src/main/php/lang/meta/FromSyntaxTree.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,20 @@ private function parse($code, $resolver) {

$params= '';
foreach ($signature->parameters as $param) {
$params.= ', $'.$param->name;
$params.=
', '.
($param->type ? $param->type->literal() : '').
($param->variadic ? '...' : '').
($param->reference ? ' &$': ' $').
$param->name.
($param->default ? '='.$param->default->expression : '')
;
}
$return= $signature->returns ? ':'.$signature->returns->literal() : '';
if (0 === strncmp($code, ' throw ', 7)) {
return new Code('function('.substr($params, 2).') {'.$code.'; }');
return new Code('function('.substr($params, 2).')'.$return.' {'.$code.'; }');
} else {
return new Code('function('.substr($params, 2).') { return'.$code.'; }');
return new Code('function('.substr($params, 2).')'.$return.' { return'.$code.'; }');
}
});

Expand All @@ -114,9 +122,17 @@ private function parse($code, $resolver) {

$params= '';
foreach ($signature->parameters as $param) {
$params.= ', $'.$param->name;
$params.=
', '.
($param->type ? $param->type->literal() : '').
($param->variadic ? '...' : '').
($param->reference ? ' &$': ' $').
$param->name.
($param->default ? '='.$param->default->expression : '')
;
}
return new Code('function('.substr($params, 2).')'.$code.' }');
$return= $signature->returns ? ':'.$signature->returns->literal() : '';
return new Code('function('.substr($params, 2).')'.$return.$code.' }');
};

// Function expressions and function expressions used as statement
Expand Down
43 changes: 43 additions & 0 deletions src/test/php/lang/reflection/unittest/AnnotationTest.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace lang\reflection\unittest;

use ReflectionFunction;
use lang\reflection\{Annotation, CannotInstantiate, InvocationFailed};
use lang\{IllegalStateException, Reflection, XPClass};
use test\{Assert, Expect, Test, Values};
Expand Down Expand Up @@ -170,6 +171,48 @@ public function with_array_lambda() {
Assert::equals(6100, $f[0]());
}

#[Test, Values(['function(\$param= null) { }', 'fn(\$param= null) => null'])]
public function with_optional_param($function) {
$t= $this->declare('{}', '#[Annotated(eval: "'.$function.'")]');
$f= $t->annotation(Annotated::class)->argument(0);

Assert::true((new ReflectionFunction($f))->getParameters()[0]->isOptional());
}

#[Test, Values(['function(... \$param) { }', 'fn(... \$param) => null'])]
public function with_variadic_param($function) {
$t= $this->declare('{}', '#[Annotated(eval: "'.$function.'")]');
$f= $t->annotation(Annotated::class)->argument(0);

Assert::true((new ReflectionFunction($f))->getParameters()[0]->isVariadic());
}

#[Test, Values(['function(&\$param) { }', 'fn(&\$param) => null'])]
public function with_reference_param($function) {
$t= $this->declare('{}', '#[Annotated(eval: "'.$function.'")]');
$f= $t->annotation(Annotated::class)->argument(0);

Assert::true((new ReflectionFunction($f))->getParameters()[0]->isPassedByReference());
}

#[Test, Values(['function(int \$param) { }', 'fn(int \$param) => null'])]
public function with_param_type($function) {
$t= $this->declare('{}', '#[Annotated(eval: "'.$function.'")]');
$f= $t->annotation(Annotated::class)->argument(0);

$type= (new ReflectionFunction($f))->getParameters()[0]->getType();
Assert::equals('int', PHP_VERSION_ID >= 70100 ? $type->getName() : (string)$type);
}

#[Test, Values(['function(): int { return 6100; }', 'fn(): int => 6100'])]
public function with_return_type($function) {
$t= $this->declare('{}', '#[Annotated(eval: "'.$function.'")]');
$f= $t->annotation(Annotated::class)->argument(0);

$type= (new ReflectionFunction($f))->getReturnType();
Assert::equals('int', PHP_VERSION_ID >= 70100 ? $type->getName() : (string)$type);
}

#[Test]
public function multiple() {
$t= $this->declare('{}', '#[Annotated, Enumeration([])]');
Expand Down

0 comments on commit e70037e

Please sign in to comment.