diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index d2ce08b..a62c7e8 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -171,7 +171,7 @@ public function __construct() { $expr= new InvokeExpression($expr, $arguments, $token->line); } - return new ScopeExpression($scope, $expr, $left->line); + return new ScopeExpression($scope, $expr, $token->line); }); $this->infix('(', 100, function($parse, $token, $left) { @@ -482,6 +482,40 @@ public function __construct() { }); $this->prefix('(name)', 0, function($parse, $token) { + static $types= ['(' => 1, ')' => 1, ',' => 1, '?' => 1, ':' => 1, '|' => 1, '&' => 1]; + + // Disambiguate `Class` from `const < expr` by looking ahead + if ('<' === $parse->token->value) { + $generic= true; + $level= 1; + $skipped= [$parse->token]; + while ($level > 0) { + $parse->forward(); + $skipped[]= $parse->token; + + if ('<' === $parse->token->symbol->id) { + $level++; + } else if ('token->symbol->id) { + $level++; + } else if ('>' === $parse->token->symbol->id) { + $level--; + } else if ('>>' === $parse->token->symbol->id) { + $level-= 2; + } else if ('name' !== $parse->token->kind && !isset($types[$parse->token->value])) { + $generic= false; + break; + } + } + + $parse->queue= $parse->queue ? array_merge($skipped, $parse->queue) : $skipped; + if ($generic) { + $parse->token= $token; + return $this->type($parse, false); + } + + $parse->forward(); + } + return new Literal($token->value, $token->line); }); 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 ae8c2e5..b24d634 100755 --- a/src/test/php/lang/ast/unittest/parse/MembersTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/MembersTest.class.php @@ -19,7 +19,7 @@ Variable, Parameter }; -use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue}; +use lang\ast\types\{IsFunction, IsLiteral, IsNullable, IsUnion, IsValue, IsGeneric}; use test\{Assert, Test, Values}; class MembersTest extends ParseTest { @@ -297,6 +297,14 @@ public function class_resolution() { ); } + #[Test] + public function generic_class_resolution() { + $this->assertParsed( + [new ScopeExpression(new IsGeneric(new IsValue('\\A'), [new IsValue('\\T')]), new Literal('class', self::LINE), self::LINE)], + 'A::class;' + ); + } + #[Test] public function instance_method_invocation() { $this->assertParsed( diff --git a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php index 474a072..6040b96 100755 --- a/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/OperatorTest.class.php @@ -332,4 +332,17 @@ public function multiple_semicolons() { ';; $a= 1 ;;; $b= 2;' ); } + + #[Test] + public function const_less_than_const() { + $this->assertParsed( + [new BinaryExpression( + new Literal('a', self::LINE), + '<', + new Literal('b', self::LINE), + self::LINE + )], + 'a < b;' + ); + } } \ No newline at end of file