diff --git a/src/main/php/lang/ast/syntax/PHP.class.php b/src/main/php/lang/ast/syntax/PHP.class.php index 979e34f..0ee6813 100755 --- a/src/main/php/lang/ast/syntax/PHP.class.php +++ b/src/main/php/lang/ast/syntax/PHP.class.php @@ -346,26 +346,22 @@ public function __construct() { [$arguments, $callable]= $this->arguments($parse); $parse->expecting(')', 'new arguments'); + // Anonymous classes + if (null === $type) { + $class= $this->class($parse, null, null, [], $parse->token->line); + $class->annotations= $annotations; + $new= new NewClassExpression($class, null, $token->line); + } else { + $new= new NewExpression($type, null, $token->line); + } + // Resolve ambiguity by looking ahead: `new T(...)` which is a first-class // callable reference vs. `new T(...$it)` - a call with an unpacked argument if ($callable) { - if (null === $type) { - $class= $this->class($parse, null); - $class->annotations= $annotations; - $new= new NewClassExpression($class, null, $token->line); - } else { - $new= new NewExpression($type, null, $token->line); - } - return new CallableNewExpression($new, $arguments, $token->line); - } - - if (null === $type) { - $class= $this->class($parse, null); - $class->annotations= $annotations; - return new NewClassExpression($class, $arguments, $token->line); } else { - return new NewExpression($type, $arguments, $token->line); + $new->arguments= $arguments; + return $new; } }); @@ -911,7 +907,47 @@ public function __construct() { $parse->comment= null; $name= $this->type($parse, false); - return $this->class($parse, $name, $comment); + // Primary constructors + if ('(' === $parse->token->value) { + $parse->expecting('(', 'class'); + $parameters= $this->parameters($parse); + $parse->expecting(')', 'class'); + + // Check for call to parent constructor via `extends B(...)`: + $parent= null; + $statements= []; + if ('extends' === $parse->token->value) { + $parse->forward(); + $parent= $this->type($parse, false); + + if ('(' === $parse->token->value) { + $parse->expecting('(', 'base arguments'); + [$arguments, $callable]= $this->arguments($parse); + $parse->expecting(')', 'base arguments'); + $statements= [new ScopeExpression('parent', new InvokeExpression( + new Literal('__construct'), + $arguments + ))]; + } + } + + $constructor= new Method( + ['public'], + '__construct', + new Signature($parameters, null, false, $token->line), + new Block($statements, $token->line), + null, + $comment, + $token->line, + ); + $class= $this->class($parse, $name, $comment, [], $token->line); + $class->body= ['__construct()' => $constructor] + $class->body; + $class->parent= $parent; + } else { + $class= $this->class($parse, $name, $comment, [], $token->line); + } + + return $class; }); $this->stmt('interface', function($parse, $token) { @@ -1751,8 +1787,7 @@ public function block($parse) { } } - public function class($parse, $name, $comment= null, $modifiers= []) { - $line= $parse->token->line; + public function class($parse, $name, $comment= null, $modifiers= [], $line= null) { $parent= null; if ('extends' === $parse->token->value) { $parse->forward(); diff --git a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php index cafb2cb..5096ecc 100755 --- a/src/test/php/lang/ast/unittest/parse/TypesTest.class.php +++ b/src/test/php/lang/ast/unittest/parse/TypesTest.class.php @@ -2,16 +2,21 @@ use lang\ast\Errors; use lang\ast\nodes\{ + Block, ClassDeclaration, InterfaceDeclaration, EnumDeclaration, EnumCase, + Method, NamespaceDeclaration, + Parameter, + Property, + Signature, TraitDeclaration, UseExpression, Literal }; -use lang\ast\types\IsValue; +use lang\ast\types\{IsValue, IsLiteral}; use test\{Assert, Expect, Test}; class TypesTest extends ParseTest { @@ -173,6 +178,32 @@ public function class_with_trait_and_aliases() { }'); } + #[Test] + public function primary_constructor() { + $lo= new Property(['public'], 'lo', new IsLiteral('int'), null, null, null, self::LINE); + $hi= new Property(['public'], 'hi', new IsLiteral('int'), null, null, null, self::LINE); + $class= new ClassDeclaration([], new IsValue('\\Range'), null, [], [], null, null, self::LINE); + $class->declare(new Method( + ['public'], + '__construct', + new Signature( + [ + new Parameter('lo', new IsLiteral('int'), null, false, false, $lo, null, null, self::LINE), + new Parameter('hi', new IsLiteral('int'), null, false, false, $hi, null, null, self::LINE), + ], + null, + false, + self::LINE + ), + new Block([], self::LINE), + null, + null, + self::LINE + )); + + $this->assertParsed([$class], 'class Range(public int $lo, public int $hi) { }'); + } + #[Test] public function class_in_namespace() { $this->assertParsed(