Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 53 additions & 18 deletions src/main/php/lang/ast/syntax/PHP.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
});

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
33 changes: 32 additions & 1 deletion src/test/php/lang/ast/unittest/parse/TypesTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down
Loading