Skip to content

Commit 2e9da03

Browse files
committed
PHP 8.4 | File::getMethodParameters(): add support for asymmetric visibility
Implements the suggested support per #851 (comment). Includes tests. Follow up on 871 Partial fix for 851 Related to 734
1 parent 022c1a1 commit 2e9da03

File tree

3 files changed

+170
-21
lines changed

3 files changed

+170
-21
lines changed

src/Files/File.php

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,10 @@ public function getDeclarationName($stackPtr)
13541354
* 'readonly_token' => integer, // The stack pointer to the readonly modifier token.
13551355
* // This index will only be set if the property is readonly.
13561356
*
1357+
* ... and if the promoted property uses asymmetric visibility, these additional array indexes will also be available:
1358+
* 'set_visibility' => string, // The property set-visibility as declared.
1359+
* 'set_visibility_token' => integer, // The stack pointer to the set-visibility modifier token.
1360+
*
13571361
* @param int $stackPtr The position in the stack of the function token
13581362
* to acquire the parameters for.
13591363
*
@@ -1406,10 +1410,11 @@ public function getMethodParameters($stackPtr)
14061410
$variadicToken = false;
14071411
$typeHint = '';
14081412
$typeHintToken = false;
1409-
$typeHintEndToken = false;
1410-
$nullableType = false;
1411-
$visibilityToken = null;
1412-
$readonlyToken = null;
1413+
$typeHintEndToken = false;
1414+
$nullableType = false;
1415+
$visibilityToken = null;
1416+
$setVisibilityToken = null;
1417+
$readonlyToken = null;
14131418

14141419
for ($i = $paramStart; $i <= $closer; $i++) {
14151420
// Check to see if this token has a parenthesis or bracket opener. If it does
@@ -1541,6 +1546,13 @@ public function getMethodParameters($stackPtr)
15411546
$visibilityToken = $i;
15421547
}
15431548
break;
1549+
case T_PUBLIC_SET:
1550+
case T_PROTECTED_SET:
1551+
case T_PRIVATE_SET:
1552+
if ($defaultStart === null) {
1553+
$setVisibilityToken = $i;
1554+
}
1555+
break;
15441556
case T_READONLY:
15451557
if ($defaultStart === null) {
15461558
$readonlyToken = $i;
@@ -1575,16 +1587,21 @@ public function getMethodParameters($stackPtr)
15751587
$vars[$paramCount]['type_hint_end_token'] = $typeHintEndToken;
15761588
$vars[$paramCount]['nullable_type'] = $nullableType;
15771589

1578-
if ($visibilityToken !== null || $readonlyToken !== null) {
1590+
if ($visibilityToken !== null || $setVisibilityToken !== null || $readonlyToken !== null) {
15791591
$vars[$paramCount]['property_visibility'] = 'public';
15801592
$vars[$paramCount]['visibility_token'] = false;
1581-
$vars[$paramCount]['property_readonly'] = false;
15821593

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

1599+
if ($setVisibilityToken !== null) {
1600+
$vars[$paramCount]['set_visibility'] = $this->tokens[$setVisibilityToken]['content'];
1601+
$vars[$paramCount]['set_visibility_token'] = $setVisibilityToken;
1602+
}
1603+
1604+
$vars[$paramCount]['property_readonly'] = false;
15881605
if ($readonlyToken !== null) {
15891606
$vars[$paramCount]['property_readonly'] = true;
15901607
$vars[$paramCount]['readonly_token'] = $readonlyToken;
@@ -1598,21 +1615,22 @@ public function getMethodParameters($stackPtr)
15981615
}
15991616

16001617
// Reset the vars, as we are about to process the next parameter.
1601-
$currVar = null;
1602-
$paramStart = ($i + 1);
1603-
$defaultStart = null;
1604-
$equalToken = null;
1605-
$hasAttributes = false;
1606-
$passByReference = false;
1607-
$referenceToken = false;
1608-
$variableLength = false;
1609-
$variadicToken = false;
1610-
$typeHint = '';
1611-
$typeHintToken = false;
1612-
$typeHintEndToken = false;
1613-
$nullableType = false;
1614-
$visibilityToken = null;
1615-
$readonlyToken = null;
1618+
$currVar = null;
1619+
$paramStart = ($i + 1);
1620+
$defaultStart = null;
1621+
$equalToken = null;
1622+
$hasAttributes = false;
1623+
$passByReference = false;
1624+
$referenceToken = false;
1625+
$variableLength = false;
1626+
$variadicToken = false;
1627+
$typeHint = '';
1628+
$typeHintToken = false;
1629+
$typeHintEndToken = false;
1630+
$nullableType = false;
1631+
$visibilityToken = null;
1632+
$setVisibilityToken = null;
1633+
$readonlyToken = null;
16161634

16171635
$paramCount++;
16181636
break;

tests/Core/Files/File/GetMethodParametersTest.inc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,17 @@ class ConstructorPropertyPromotionWithOnlyReadOnly {
217217
public function __construct(readonly Foo&Bar $promotedProp, readonly ?bool $promotedToo,) {}
218218
}
219219

220+
class ConstructorPropertyPromotionWithAsymVisibility {
221+
/* testPHP84ConstructorPropertyPromotionWithAsymVisibility */
222+
public function __construct(
223+
protected(set) string|Book $book,
224+
public private(set) ?Publisher $publisher,
225+
Private(set) PROTECTED Author $author,
226+
readonly public(set) int $pubYear,
227+
protected(set) $illegalMissingType,
228+
) {}
229+
}
230+
220231
/* testPHP8ConstructorPropertyPromotionGlobalFunction */
221232
// Intentional fatal error. Property promotion not allowed in non-constructor, but that's not the concern of this method.
222233
function globalFunction(private $x) {}

tests/Core/Files/File/GetMethodParametersTest.php

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2190,6 +2190,122 @@ public function testPHP81ConstructorPropertyPromotionWithOnlyReadOnly()
21902190
}//end testPHP81ConstructorPropertyPromotionWithOnlyReadOnly()
21912191

21922192

2193+
/**
2194+
* Verify recognition of PHP8 constructor with property promotion using PHP 8.4 asymmetric visibility.
2195+
*
2196+
* @return void
2197+
*/
2198+
public function testPHP84ConstructorPropertyPromotionWithAsymVisibility()
2199+
{
2200+
// Offsets are relative to the T_FUNCTION token.
2201+
$expected = [];
2202+
$expected[0] = [
2203+
'token' => 12,
2204+
'name' => '$book',
2205+
'content' => 'protected(set) string|Book $book',
2206+
'has_attributes' => false,
2207+
'pass_by_reference' => false,
2208+
'reference_token' => false,
2209+
'variable_length' => false,
2210+
'variadic_token' => false,
2211+
'type_hint' => 'string|Book',
2212+
'type_hint_token' => 8,
2213+
'type_hint_end_token' => 10,
2214+
'nullable_type' => false,
2215+
'property_visibility' => 'public',
2216+
'visibility_token' => false,
2217+
'set_visibility' => 'protected(set)',
2218+
'set_visibility_token' => 6,
2219+
'property_readonly' => false,
2220+
'comma_token' => 13,
2221+
];
2222+
$expected[1] = [
2223+
'token' => 23,
2224+
'name' => '$publisher',
2225+
'content' => 'public private(set) ?Publisher $publisher',
2226+
'has_attributes' => false,
2227+
'pass_by_reference' => false,
2228+
'reference_token' => false,
2229+
'variable_length' => false,
2230+
'variadic_token' => false,
2231+
'type_hint' => '?Publisher',
2232+
'type_hint_token' => 21,
2233+
'type_hint_end_token' => 21,
2234+
'nullable_type' => true,
2235+
'property_visibility' => 'public',
2236+
'visibility_token' => 16,
2237+
'set_visibility' => 'private(set)',
2238+
'set_visibility_token' => 18,
2239+
'property_readonly' => false,
2240+
'comma_token' => 24,
2241+
];
2242+
$expected[2] = [
2243+
'token' => 33,
2244+
'name' => '$author',
2245+
'content' => 'Private(set) PROTECTED Author $author',
2246+
'has_attributes' => false,
2247+
'pass_by_reference' => false,
2248+
'reference_token' => false,
2249+
'variable_length' => false,
2250+
'variadic_token' => false,
2251+
'type_hint' => 'Author',
2252+
'type_hint_token' => 31,
2253+
'type_hint_end_token' => 31,
2254+
'nullable_type' => false,
2255+
'property_visibility' => 'PROTECTED',
2256+
'visibility_token' => 29,
2257+
'set_visibility' => 'Private(set)',
2258+
'set_visibility_token' => 27,
2259+
'property_readonly' => false,
2260+
'comma_token' => 34,
2261+
];
2262+
$expected[3] = [
2263+
'token' => 43,
2264+
'name' => '$pubYear',
2265+
'content' => 'readonly public(set) int $pubYear',
2266+
'has_attributes' => false,
2267+
'pass_by_reference' => false,
2268+
'reference_token' => false,
2269+
'variable_length' => false,
2270+
'variadic_token' => false,
2271+
'type_hint' => 'int',
2272+
'type_hint_token' => 41,
2273+
'type_hint_end_token' => 41,
2274+
'nullable_type' => false,
2275+
'property_visibility' => 'public',
2276+
'visibility_token' => false,
2277+
'set_visibility' => 'public(set)',
2278+
'set_visibility_token' => 39,
2279+
'property_readonly' => true,
2280+
'readonly_token' => 37,
2281+
'comma_token' => 44,
2282+
];
2283+
$expected[4] = [
2284+
'token' => 49,
2285+
'name' => '$illegalMissingType',
2286+
'content' => 'protected(set) $illegalMissingType',
2287+
'has_attributes' => false,
2288+
'pass_by_reference' => false,
2289+
'reference_token' => false,
2290+
'variable_length' => false,
2291+
'variadic_token' => false,
2292+
'type_hint' => '',
2293+
'type_hint_token' => false,
2294+
'type_hint_end_token' => false,
2295+
'nullable_type' => false,
2296+
'property_visibility' => 'public',
2297+
'visibility_token' => false,
2298+
'set_visibility' => 'protected(set)',
2299+
'set_visibility_token' => 47,
2300+
'property_readonly' => false,
2301+
'comma_token' => 50,
2302+
];
2303+
2304+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
2305+
2306+
}//end testPHP84ConstructorPropertyPromotionWithAsymVisibility()
2307+
2308+
21932309
/**
21942310
* Verify behaviour when a non-constructor function uses PHP 8 property promotion syntax.
21952311
*
@@ -3214,6 +3330,10 @@ private function getMethodParametersTestHelper($commentString, $expected, $targe
32143330
$expected[$key]['visibility_token'] += $target;
32153331
}
32163332

3333+
if (isset($param['set_visibility_token']) === true && is_int($param['set_visibility_token']) === true) {
3334+
$expected[$key]['set_visibility_token'] += $target;
3335+
}
3336+
32173337
if (isset($param['readonly_token']) === true) {
32183338
$expected[$key]['readonly_token'] += $target;
32193339
}

0 commit comments

Comments
 (0)