diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 59cb96f..a657254 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1476,8 +1476,35 @@ private function annotations($parse, $context) { return $annotations; } + private function modifier($parse) { + static $hooks= ['set' => true]; + + $modifier= $parse->token->value; + $parse->forward(); + + if ('(' === $parse->token->value) { + $token= $parse->token; + $parse->expecting('(', 'modifiers'); + + if (isset($hooks[$parse->token->value])) { + $modifier.= '('.$parse->token->value.')'; + $parse->forward(); + $parse->expecting(')', 'modifiers'); + } else { + array_unshift($parse->queue, $parse->token); + $parse->token= $token; + } + } + return $modifier; + } + private function parameters($parse) { - static $promotion= ['private' => true, 'protected' => true, 'public' => true]; + static $promotion= [ + 'private' => true, + 'protected' => true, + 'public' => true, + 'readonly' => true, + ]; $parameters= []; while (')' !== $parse->token->value) { @@ -1490,14 +1517,10 @@ private function parameters($parse) { $line= $parse->token->line; if ('name' === $parse->token->kind && isset($promotion[$parse->token->value])) { - $promote= $parse->token->value; - $parse->forward(); - - // It would be better to use an array for promote, but this way we keep BC - if ('readonly' === $parse->token->value) { - $promote.= ' readonly'; - $parse->forward(); - } + $promote= []; + do { + $promote[]= $this->modifier($parse); + } while (isset($promotion[$parse->token->value])); } else { $promote= null; } @@ -1566,8 +1589,7 @@ public function typeBody($parse) { $meta= []; while ('}' !== $parse->token->value) { if (isset($modifier[$parse->token->value])) { - $modifiers[]= $parse->token->value; - $parse->forward(); + $modifiers[]= $this->modifier($parse); } else if ($f= $this->body[$parse->token->value] ?? $this->body['@'.$parse->token->kind] ?? null) { $f($parse, $body, $meta, $modifiers); $modifiers= []; diff --git a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php index b24d634..e633de8 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -380,4 +380,21 @@ public function typed_constants() { $this->assertParsed([$class], 'class A { const int T = 1, S = 2, string I = "i"; }'); } + + #[Test] + public function asymmetric_property() { + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $class->declare(new Property(['public', 'private(set)'], 'a', new Type('int'), null, null, null, self::LINE)); + + $this->assertParsed([$class], 'class A { public private(set) int $a; }'); + } + + #[Test] + public function asymmetric_property_as_constructor_argument() { + $params= [new Parameter('a', new IsLiteral('int'), null, false, false, ['private(set)'], null, null, self::LINE)]; + $class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE); + $class->declare(new Method(['public'], '__construct', new Signature($params, null, false, self::LINE), [], null, null, self::LINE)); + + $this->assertParsed([$class], 'class A { public function __construct(private(set) int $a) { } }'); + } } \ No newline at end of file