From 990744db9f1a0d447c1bfb3f81d225e1b244a3da Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sun, 4 Dec 2022 20:08:16 +0100 Subject: [PATCH] Fix type parsing in type casts --- ChangeLog.md | 7 ++ src/main/php/lang/ast/syntax/PHP.class.php | 15 ++-- .../ast/unittest/parse/CastTest.class.php | 78 +++++++++++++++++++ 3 files changed, 90 insertions(+), 10 deletions(-) create mode 100755 src/test/php/lang/ast/unittest/parse/CastTest.class.php diff --git a/ChangeLog.md b/ChangeLog.md index cd85e5b..d3d3d7e 100755 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,13 @@ XP AST ChangeLog ## ?.?.? / ????-??-?? +## 9.2.1 / 2022-12-04 + +* Fixed type parsing in type casts: + - Arrays, maps and generics with nullables, e.g. `(array)$v` + - Intersection and union types, e.g. `(int|string)$v`. + (@thekid) + ## 9.2.0 / 2022-11-12 * Added support for omitting expressions in destructuring assignments, diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index fbcdd55..dee1daa 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -243,13 +243,7 @@ public function __construct() { // // Resolve by looking ahead after the closing ")" $this->prefix('(', 0, function($parse, $token) { - static $types= [ - '<' => true, - '>' => true, - ',' => true, - '?' => true, - ':' => true - ]; + static $types= ['<' => 1, '>' => 1, '<<' => 1, '>>' => 1, ',' => 1, '?' => 1, ' 1, ':' => 1, '|' => 1, '&' => 1]; $skipped= [$token, $parse->token]; $cast= true; @@ -267,10 +261,11 @@ public function __construct() { } $parse->queue= $parse->queue ? array_merge($skipped, $parse->queue) : $skipped; + if ($cast && ('operator' !== $parse->token->kind || '(' === $parse->token->value || '[' === $parse->token->value)) { $parse->forward(); $parse->expecting('(', 'cast'); - $type= $this->type0($parse, false); + $type= $this->type($parse, false); $parse->expecting(')', 'cast'); return new CastExpression($type, $this->expression($parse, 0), $token->line); @@ -1220,7 +1215,7 @@ private function type0($parse, $optional) { // Resolve ambiguity between short open tag and nullables as in if ('token->value) { - $parse->queue[]= new Token(self::symbol('?'), '(operator)', '?'); + array_unshift($parse->queue, new Token(self::symbol('?'), '(operator)', '?')); $parse->token->value= '<'; } @@ -1244,7 +1239,7 @@ private function type0($parse, $optional) { if ('>' === $parse->token->symbol->id) { break; } else if ('>>' === $parse->token->value) { - $parse->queue[]= $parse->token= new Token(self::symbol('>')); + array_unshift($parse->queue, $parse->token= new Token(self::symbol('>'))); break; } } while (',' === $parse->token->value && true | $parse->forward()); diff --git a/src/test/php/lang/ast/unittest/parse/CastTest.class.php b/src/test/php/lang/ast/unittest/parse/CastTest.class.php new file mode 100755 index 0000000..86441b5 --- /dev/null +++ b/src/test/php/lang/ast/unittest/parse/CastTest.class.php @@ -0,0 +1,78 @@ +', new IsGeneric(new IsValue('List'), [new IsLiteral('int')])]; + yield ['Map', new IsGeneric(new IsValue('Map'), [new IsLiteral('string'), new IsValue('Value')])]; + } + + #[Test, Values('types')] + public function simple($type, $expected) { + $this->assertParsed( + [new CastExpression($expected, new Variable('a', self::LINE), self::LINE)], + '('.$type.')$a;' + ); + } + + #[Test, Values('types')] + public function array_of($type, $expected) { + $this->assertParsed( + [new CastExpression(new IsArray($expected), new Variable('a', self::LINE), self::LINE)], + '(array<'.$type.'>)$a;' + ); + } + + #[Test, Values('types')] + public function map_of($type, $expected) { + $this->assertParsed( + [new CastExpression(new IsMap(new IsLiteral('string'), $expected), new Variable('a', self::LINE), self::LINE)], + '(array)$a;' + ); + } + + #[Test, Values('types')] + public function union_of($type, $expected) { + $this->assertParsed( + [new CastExpression(new IsUnion([new IsLiteral('string'), $expected]), new Variable('a', self::LINE), self::LINE)], + '(string|'.$type.')$a;' + ); + } + + #[Test, Values('types')] + public function intersection_of($type, $expected) { + $this->assertParsed( + [new CastExpression(new IsIntersection([new IsLiteral('string'), $expected]), new Variable('a', self::LINE), self::LINE)], + '(string&'.$type.')$a;' + ); + } +} \ No newline at end of file