Skip to content

Commit

Permalink
PHP 8.0 | File::getMethodParameters(): add support for PHP 8 construc…
Browse files Browse the repository at this point in the history
…tor 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
  • Loading branch information
jrfnl committed Nov 2, 2020
1 parent 457afdf commit 3eea1b0
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 1 deletion.
20 changes: 19 additions & 1 deletion src/Files/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -1301,11 +1301,15 @@ public function getDeclarationName($stackPtr)
* )
* </code>
*
* Parameters with default values have an additional array indexes of:
* Parameters with default values have additional array indexes of:
* 'default' => string, // The full content of the default value.
* 'default_token' => integer, // The stack pointer to the start of the default value.
* 'default_equal_token' => integer, // The stack pointer to the equals sign.
*
* Parameters declared using PHP 8 constructor property promotion, have these additional array indexes:
* 'property_visibility' => string, // The property visibility as declared.
* 'visibility_token' => integer, // The stack pointer to the visibility modifier token.
*
* @param int $stackPtr The position in the stack of the function token
* to acquire the parameters for.
*
Expand Down Expand Up @@ -1359,6 +1363,7 @@ public function getMethodParameters($stackPtr)
$typeHintToken = false;
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;

for ($i = $paramStart; $i <= $closer; $i++) {
// Check to see if this token has a parenthesis or bracket opener. If it does
Expand Down Expand Up @@ -1470,6 +1475,13 @@ public function getMethodParameters($stackPtr)
$typeHintEndToken = $i;
}
break;
case T_PUBLIC:
case T_PROTECTED:
case T_PRIVATE:
if ($defaultStart === null) {
$visibilityToken = $i;
}
break;
case T_CLOSE_PARENTHESIS:
case T_COMMA:
// If it's null, then there must be no parameters for this
Expand Down Expand Up @@ -1498,6 +1510,11 @@ public function getMethodParameters($stackPtr)
$vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken;
$vars[$paramCount]['nullable_type'] = $nullableType;

if ($visibilityToken !== null) {
$vars[$paramCount]['property_visibility'] = $this->tokens[$visibilityToken]['content'];
$vars[$paramCount]['visibility_token'] = $visibilityToken;
}

if ($this->tokens[$i]['code'] === T_COMMA) {
$vars[$paramCount]['comma_token'] = $i;
} else {
Expand All @@ -1517,6 +1534,7 @@ public function getMethodParameters($stackPtr)
$typeHintToken = false;
$typeHintEndToken = false;
$nullableType = false;
$visibilityToken = null;

$paramCount++;
break;
Expand Down
32 changes: 32 additions & 0 deletions tests/Core/File/GetMethodParametersTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,35 @@ function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {}

class ConstructorPropertyPromotionNoTypes {
/* testPHP8ConstructorPropertyPromotionNoTypes */
public function __construct(
public $x = 0.0,
protected $y = '',
private $z = null,
) {}
}

class ConstructorPropertyPromotionWithTypes {
/* testPHP8ConstructorPropertyPromotionWithTypes */
public function __construct(protected float|int $x, public ?string &$y = 'test', private mixed $z) {}
}

class ConstructorPropertyPromotionAndNormalParams {
/* testPHP8ConstructorPropertyPromotionAndNormalParam */
public function __construct(public int $promotedProp, ?int $normalArg) {}
}

/* testPHP8ConstructorPropertyPromotionGlobalFunction */
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
function globalFunction(private $x) {}

abstract class ConstructorPropertyPromotionAbstractMethod {
/* testPHP8ConstructorPropertyPromotionAbstractMethod */
// Intentional fatal error.
// 1. Property promotion not allowed in abstract method, but that's not the concern of this method.
// 2. Variadic arguments not allowed in property promotion, but that's not the concern of this method.
// 3. The callable type is not supported for properties, but that's not the concern of this method.
abstract public function __construct(public callable $y, private ...$x);
}
172 changes: 172 additions & 0 deletions tests/Core/File/GetMethodParametersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,178 @@ public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
}//end testPHP8DuplicateTypeInUnionWhitespaceAndComment()


/**
* Verify recognition of PHP8 constructor property promotion without type declaration, with defaults.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionNoTypes()
{
$expected = [];
$expected[0] = [
'name' => '$x',
'content' => 'public $x = 0.0',
'default' => '0.0',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'public',
];
$expected[1] = [
'name' => '$y',
'content' => 'protected $y = \'\'',
'default' => "''",
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'protected',
];
$expected[2] = [
'name' => '$z',
'content' => 'private $z = null',
'default' => 'null',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'private',
];

$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);

}//end testPHP8ConstructorPropertyPromotionNoTypes()


/**
* Verify recognition of PHP8 constructor property promotion with type declarations.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionWithTypes()
{
$expected = [];
$expected[0] = [
'name' => '$x',
'content' => 'protected float|int $x',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'float|int',
'nullable_type' => false,
'property_visibility' => 'protected',
];
$expected[1] = [
'name' => '$y',
'content' => 'public ?string &$y = \'test\'',
'default' => "'test'",
'pass_by_reference' => true,
'variable_length' => false,
'type_hint' => '?string',
'nullable_type' => true,
'property_visibility' => 'public',
];
$expected[2] = [
'name' => '$z',
'content' => 'private mixed $z',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'mixed',
'nullable_type' => false,
'property_visibility' => 'private',
];

$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);

}//end testPHP8ConstructorPropertyPromotionWithTypes()


/**
* Verify recognition of PHP8 constructor with both property promotion as well as normal parameters.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionAndNormalParam()
{
$expected = [];
$expected[0] = [
'name' => '$promotedProp',
'content' => 'public int $promotedProp',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'int',
'nullable_type' => false,
'property_visibility' => 'public',
];
$expected[1] = [
'name' => '$normalArg',
'content' => '?int $normalArg',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '?int',
'nullable_type' => true,
];

$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);

}//end testPHP8ConstructorPropertyPromotionAndNormalParam()


/**
* Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionGlobalFunction()
{
$expected = [];
$expected[0] = [
'name' => '$x',
'content' => 'private $x',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'private',
];

$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);

}//end testPHP8ConstructorPropertyPromotionGlobalFunction()


/**
* Verify behaviour when an abstract constructor uses PHP 8 property promotion syntax.
*
* @return void
*/
public function testPHP8ConstructorPropertyPromotionAbstractMethod()
{
$expected = [];
$expected[0] = [
'name' => '$y',
'content' => 'public callable $y',
'pass_by_reference' => false,
'variable_length' => false,
'type_hint' => 'callable',
'nullable_type' => false,
'property_visibility' => 'public',
];
$expected[1] = [
'name' => '$x',
'content' => 'private ...$x',
'pass_by_reference' => false,
'variable_length' => true,
'type_hint' => '',
'nullable_type' => false,
'property_visibility' => 'private',
];

$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);

}//end testPHP8ConstructorPropertyPromotionAbstractMethod()


/**
* Test helper.
*
Expand Down

0 comments on commit 3eea1b0

Please sign in to comment.