From 8c2097179fe3d63d8f1e0d279daa6806f1e053c6 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 24 Aug 2024 11:52:16 +0200 Subject: [PATCH 1/6] Add syntactic support for asymmetric visibility See https://wiki.php.net/rfc/asymmetric-visibility-v2 --- src/main/php/lang/ast/syntax/PHP.class.php | 11 ++++++++++- .../php/lang/ast/unittest/parse/MembersTest.class.php | 8 ++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 817cd3c..2849f65 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1566,8 +1566,17 @@ public function typeBody($parse) { $meta= []; while ('}' !== $parse->token->value) { if (isset($modifier[$parse->token->value])) { - $modifiers[]= $parse->token->value; + $token= $parse->token->value; $parse->forward(); + + if ('(' === $parse->token->value) { + $parse->expecting('(', 'modifiers'); + $token.= '('.$parse->token->value.')'; + $parse->forward(); + $parse->expecting(')', 'modifiers'); + } + + $modifiers[]= $token; } 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..fb6f4e1 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,12 @@ 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; }'); + } } \ No newline at end of file From 6a24b4f252e8359e816a229eb14653684b534268 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 24 Aug 2024 12:09:24 +0200 Subject: [PATCH 2/6] Support asymmetric visibility in promoted constructor params --- src/main/php/lang/ast/syntax/PHP.class.php | 27 +++++++++++++------ .../ast/unittest/parse/MembersTest.class.php | 9 +++++++ 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 2849f65..77e65e3 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1477,7 +1477,12 @@ private function annotations($parse, $context) { } 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 +1495,20 @@ 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'; + $promote= []; + do { + $token= $parse->token->value; $parse->forward(); - } + + if ('(' === $parse->token->value) { + $parse->expecting('(', 'modifiers'); + $token.= '('.$parse->token->value.')'; + $parse->forward(); + $parse->expecting(')', 'modifiers'); + } + + $promote[]= $token; + } while ('name' === $parse->token->kind && isset($promotion[$parse->token->value])); } else { $promote= null; } 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 fb6f4e1..e633de8 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -388,4 +388,13 @@ public function asymmetric_property() { $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 From edcab449be63ea91256509980127de188e05ceb0 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 24 Aug 2024 12:11:37 +0200 Subject: [PATCH 3/6] Extract modifier parsing into private method, deduplicating code --- src/main/php/lang/ast/syntax/PHP.class.php | 37 +++++++++------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 77e65e3..e8ac193 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1476,6 +1476,19 @@ private function annotations($parse, $context) { return $annotations; } + private function modifier($parse) { + $token= $parse->token->value; + $parse->forward(); + + if ('(' === $parse->token->value) { + $parse->expecting('(', 'modifiers'); + $token.= '('.$parse->token->value.')'; + $parse->forward(); + $parse->expecting(')', 'modifiers'); + } + return $token; + } + private function parameters($parse) { static $promotion= [ 'private' => true, @@ -1497,17 +1510,7 @@ private function parameters($parse) { if ('name' === $parse->token->kind && isset($promotion[$parse->token->value])) { $promote= []; do { - $token= $parse->token->value; - $parse->forward(); - - if ('(' === $parse->token->value) { - $parse->expecting('(', 'modifiers'); - $token.= '('.$parse->token->value.')'; - $parse->forward(); - $parse->expecting(')', 'modifiers'); - } - - $promote[]= $token; + $promote[]= $this->modifier($parse); } while ('name' === $parse->token->kind && isset($promotion[$parse->token->value])); } else { $promote= null; @@ -1577,17 +1580,7 @@ public function typeBody($parse) { $meta= []; while ('}' !== $parse->token->value) { if (isset($modifier[$parse->token->value])) { - $token= $parse->token->value; - $parse->forward(); - - if ('(' === $parse->token->value) { - $parse->expecting('(', 'modifiers'); - $token.= '('.$parse->token->value.')'; - $parse->forward(); - $parse->expecting(')', 'modifiers'); - } - - $modifiers[]= $token; + $modifiers[]= $this->modifier($parse); } else if ($f= $this->body[$parse->token->value] ?? $this->body['@'.$parse->token->kind] ?? null) { $f($parse, $body, $meta, $modifiers); $modifiers= []; From e8e38f994eac1a4b495bad96214e6b5eb8154d85 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 24 Aug 2024 12:14:08 +0200 Subject: [PATCH 4/6] Simplify code for parsing promoted parameters --- src/main/php/lang/ast/syntax/PHP.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index e8ac193..5c31237 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1511,7 +1511,7 @@ private function parameters($parse) { $promote= []; do { $promote[]= $this->modifier($parse); - } while ('name' === $parse->token->kind && isset($promotion[$parse->token->value])); + } while (isset($promotion[$parse->token->value])); } else { $promote= null; } From d7e1e1eb778ccb7f5708552d7377d08412bc365b Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 24 Aug 2024 12:37:05 +0200 Subject: [PATCH 5/6] Disambiguate function types from `private(set)` --- src/main/php/lang/ast/syntax/PHP.class.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 5c31237..114c171 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1477,16 +1477,25 @@ private function annotations($parse, $context) { } private function modifier($parse) { - $token= $parse->token->value; + $modifier= $parse->token->value; $parse->forward(); if ('(' === $parse->token->value) { + $token= $parse->token; $parse->expecting('(', 'modifiers'); - $token.= '('.$parse->token->value.')'; + + // Disambiguate function types from `private(set)` + if ('function' === $parse->token->value) { + array_unshift($parse->queue, $parse->token); + $parse->token= $token; + return $modifier; + } + + $modifier.= '('.$parse->token->value.')'; $parse->forward(); $parse->expecting(')', 'modifiers'); } - return $token; + return $modifier; } private function parameters($parse) { From 47e7ce1c1332bdf07fec57cb00cb561098fd11b3 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 24 Aug 2024 13:08:53 +0200 Subject: [PATCH 6/6] Only allow `set` hook for the moment See https://github.com/xp-framework/ast/pull/54/files#r1729938020 --- src/main/php/lang/ast/syntax/PHP.class.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 114c171..62e8d5f 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -1477,6 +1477,8 @@ private function annotations($parse, $context) { } private function modifier($parse) { + static $hooks= ['set' => true]; + $modifier= $parse->token->value; $parse->forward(); @@ -1484,16 +1486,14 @@ private function modifier($parse) { $token= $parse->token; $parse->expecting('(', 'modifiers'); - // Disambiguate function types from `private(set)` - if ('function' === $parse->token->value) { + 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; } - - $modifier.= '('.$parse->token->value.')'; - $parse->forward(); - $parse->expecting(')', 'modifiers'); } return $modifier; }