Skip to content

Commit 3eea1b0

Browse files
committed
PHP 8.0 | File::getMethodParameters(): add support for PHP 8 constructor property promotion
PHP 8 introduces constructor property promotion. This commit adds support for constructor property promotion to the `File::getMethodParameters()` method. This change introduces two new keys - `property_visibility` and `visibility_token` - to the return array which will only be included if constructor property promotion is detected. The method does not check whether property promotion is _valid_ in the function/method in which it is used. That is not the concern of this method. Includes unit tests. Ref: https://wiki.php.net/rfc/constructor_promotion
1 parent 457afdf commit 3eea1b0

File tree

3 files changed

+223
-1
lines changed

3 files changed

+223
-1
lines changed

src/Files/File.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1301,11 +1301,15 @@ public function getDeclarationName($stackPtr)
13011301
* )
13021302
* </code>
13031303
*
1304-
* Parameters with default values have an additional array indexes of:
1304+
* Parameters with default values have additional array indexes of:
13051305
* 'default' => string, // The full content of the default value.
13061306
* 'default_token' => integer, // The stack pointer to the start of the default value.
13071307
* 'default_equal_token' => integer, // The stack pointer to the equals sign.
13081308
*
1309+
* Parameters declared using PHP 8 constructor property promotion, have these additional array indexes:
1310+
* 'property_visibility' => string, // The property visibility as declared.
1311+
* 'visibility_token' => integer, // The stack pointer to the visibility modifier token.
1312+
*
13091313
* @param int $stackPtr The position in the stack of the function token
13101314
* to acquire the parameters for.
13111315
*
@@ -1359,6 +1363,7 @@ public function getMethodParameters($stackPtr)
13591363
$typeHintToken = false;
13601364
$typeHintEndToken = false;
13611365
$nullableType = false;
1366+
$visibilityToken = null;
13621367

13631368
for ($i = $paramStart; $i <= $closer; $i++) {
13641369
// Check to see if this token has a parenthesis or bracket opener. If it does
@@ -1470,6 +1475,13 @@ public function getMethodParameters($stackPtr)
14701475
$typeHintEndToken = $i;
14711476
}
14721477
break;
1478+
case T_PUBLIC:
1479+
case T_PROTECTED:
1480+
case T_PRIVATE:
1481+
if ($defaultStart === null) {
1482+
$visibilityToken = $i;
1483+
}
1484+
break;
14731485
case T_CLOSE_PARENTHESIS:
14741486
case T_COMMA:
14751487
// If it's null, then there must be no parameters for this
@@ -1498,6 +1510,11 @@ public function getMethodParameters($stackPtr)
14981510
$vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken;
14991511
$vars[$paramCount]['nullable_type'] = $nullableType;
15001512

1513+
if ($visibilityToken !== null) {
1514+
$vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content'];
1515+
$vars[$paramCount]['visibility_token'] = $visibilityToken;
1516+
}
1517+
15011518
if ($this->tokens[$i]['code'] === T_COMMA) {
15021519
$vars[$paramCount]['comma_token'] = $i;
15031520
} else {
@@ -1517,6 +1534,7 @@ public function getMethodParameters($stackPtr)
15171534
$typeHintToken = false;
15181535
$typeHintEndToken = false;
15191536
$nullableType = false;
1537+
$visibilityToken = null;
15201538

15211539
$paramCount++;
15221540
break;

tests/Core/File/GetMethodParametersTest.inc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,35 @@ function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
8585
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
8686
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
8787
function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {}
88+
89+
class ConstructorPropertyPromotionNoTypes {
90+
/* testPHP8ConstructorPropertyPromotionNoTypes */
91+
public function __construct(
92+
public $x = 0.0,
93+
protected $y = '',
94+
private $z = null,
95+
) {}
96+
}
97+
98+
class ConstructorPropertyPromotionWithTypes {
99+
/* testPHP8ConstructorPropertyPromotionWithTypes */
100+
public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {}
101+
}
102+
103+
class ConstructorPropertyPromotionAndNormalParams {
104+
/* testPHP8ConstructorPropertyPromotionAndNormalParam */
105+
public function __construct(public int $promotedProp, ?int $normalArg) {}
106+
}
107+
108+
/* testPHP8ConstructorPropertyPromotionGlobalFunction */
109+
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
110+
function globalFunction(private $x) {}
111+
112+
abstract class ConstructorPropertyPromotionAbstractMethod {
113+
/* testPHP8ConstructorPropertyPromotionAbstractMethod */
114+
// Intentional fatal error.
115+
// 1. Property promotion not allowed in abstract method, but that's not the concern of this method.
116+
// 2. Variadic arguments not allowed in property promotion, but that's not the concern of this method.
117+
// 3. The callable type is not supported for properties, but that's not the concern of this method.
118+
abstract public function __construct(public callable $y, private ...$x);
119+
}

tests/Core/File/GetMethodParametersTest.php

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,178 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
616616
}//end testPHP8DuplicateTypeInUnionWhitespaceAndComment()
617617

618618

619+
/**
620+
* Verify recognition of PHP8 constructor property promotion without type declaration, with defaults.
621+
*
622+
* @return void
623+
*/
624+
public function testPHP8ConstructorPropertyPromotionNoTypes()
625+
{
626+
$expected = [];
627+
$expected[0] = [
628+
'name' => '$x',
629+
'content' => 'public $x = 0.0',
630+
'default' => '0.0',
631+
'pass_by_reference' => false,
632+
'variable_length' => false,
633+
'type_hint' => '',
634+
'nullable_type' => false,
635+
'property_visibility' => 'public',
636+
];
637+
$expected[1] = [
638+
'name' => '$y',
639+
'content' => 'protected $y = \'\'',
640+
'default' => "''",
641+
'pass_by_reference' => false,
642+
'variable_length' => false,
643+
'type_hint' => '',
644+
'nullable_type' => false,
645+
'property_visibility' => 'protected',
646+
];
647+
$expected[2] = [
648+
'name' => '$z',
649+
'content' => 'private $z = null',
650+
'default' => 'null',
651+
'pass_by_reference' => false,
652+
'variable_length' => false,
653+
'type_hint' => '',
654+
'nullable_type' => false,
655+
'property_visibility' => 'private',
656+
];
657+
658+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
659+
660+
}//end testPHP8ConstructorPropertyPromotionNoTypes()
661+
662+
663+
/**
664+
* Verify recognition of PHP8 constructor property promotion with type declarations.
665+
*
666+
* @return void
667+
*/
668+
public function testPHP8ConstructorPropertyPromotionWithTypes()
669+
{
670+
$expected = [];
671+
$expected[0] = [
672+
'name' => '$x',
673+
'content' => 'protected float|int $x',
674+
'pass_by_reference' => false,
675+
'variable_length' => false,
676+
'type_hint' => 'float|int',
677+
'nullable_type' => false,
678+
'property_visibility' => 'protected',
679+
];
680+
$expected[1] = [
681+
'name' => '$y',
682+
'content' => 'public ?string &$y = \'test\'',
683+
'default' => "'test'",
684+
'pass_by_reference' => true,
685+
'variable_length' => false,
686+
'type_hint' => '?string',
687+
'nullable_type' => true,
688+
'property_visibility' => 'public',
689+
];
690+
$expected[2] = [
691+
'name' => '$z',
692+
'content' => 'private mixed $z',
693+
'pass_by_reference' => false,
694+
'variable_length' => false,
695+
'type_hint' => 'mixed',
696+
'nullable_type' => false,
697+
'property_visibility' => 'private',
698+
];
699+
700+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
701+
702+
}//end testPHP8ConstructorPropertyPromotionWithTypes()
703+
704+
705+
/**
706+
* Verify recognition of PHP8 constructor with both property promotion as well as normal parameters.
707+
*
708+
* @return void
709+
*/
710+
public function testPHP8ConstructorPropertyPromotionAndNormalParam()
711+
{
712+
$expected = [];
713+
$expected[0] = [
714+
'name' => '$promotedProp',
715+
'content' => 'public int $promotedProp',
716+
'pass_by_reference' => false,
717+
'variable_length' => false,
718+
'type_hint' => 'int',
719+
'nullable_type' => false,
720+
'property_visibility' => 'public',
721+
];
722+
$expected[1] = [
723+
'name' => '$normalArg',
724+
'content' => '?int $normalArg',
725+
'pass_by_reference' => false,
726+
'variable_length' => false,
727+
'type_hint' => '?int',
728+
'nullable_type' => true,
729+
];
730+
731+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
732+
733+
}//end testPHP8ConstructorPropertyPromotionAndNormalParam()
734+
735+
736+
/**
737+
* Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
738+
*
739+
* @return void
740+
*/
741+
public function testPHP8ConstructorPropertyPromotionGlobalFunction()
742+
{
743+
$expected = [];
744+
$expected[0] = [
745+
'name' => '$x',
746+
'content' => 'private $x',
747+
'pass_by_reference' => false,
748+
'variable_length' => false,
749+
'type_hint' => '',
750+
'nullable_type' => false,
751+
'property_visibility' => 'private',
752+
];
753+
754+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
755+
756+
}//end testPHP8ConstructorPropertyPromotionGlobalFunction()
757+
758+
759+
/**
760+
* Verify behaviour when an abstract constructor uses PHP 8 property promotion syntax.
761+
*
762+
* @return void
763+
*/
764+
public function testPHP8ConstructorPropertyPromotionAbstractMethod()
765+
{
766+
$expected = [];
767+
$expected[0] = [
768+
'name' => '$y',
769+
'content' => 'public callable $y',
770+
'pass_by_reference' => false,
771+
'variable_length' => false,
772+
'type_hint' => 'callable',
773+
'nullable_type' => false,
774+
'property_visibility' => 'public',
775+
];
776+
$expected[1] = [
777+
'name' => '$x',
778+
'content' => 'private ...$x',
779+
'pass_by_reference' => false,
780+
'variable_length' => true,
781+
'type_hint' => '',
782+
'nullable_type' => false,
783+
'property_visibility' => 'private',
784+
];
785+
786+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
787+
788+
}//end testPHP8ConstructorPropertyPromotionAbstractMethod()
789+
790+
619791
/**
620792
* Test helper.
621793
*

0 commit comments

Comments
 (0)