Skip to content
Merged
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
169 changes: 98 additions & 71 deletions src/main/php/lang/ast/syntax/PHP.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,77 @@ public function type0($parse, $optional= false) {
return isset($literal[$type]) ? new IsLiteral($type) : new IsValue($type);
}

/** Parses property hooks block */
private function hooks($parse) {
$hooks= [];
while ('}' !== $parse->token->value) {
if ('final' === $parse->token->value) {
$modifiers= ['final'];
$parse->forward();
} else {
$modifiers= [];
}

if ('&' === $parse->token->value) {
$byref= true;
$parse->forward();
} else {
$byref= false;
}

$hook= $parse->token->value;
$parse->forward();

if ('(' === $parse->token->value) {
$parse->forward();

if ('#[' === $parse->token->value) {
$parse->forward();
$annotations= $this->annotations($parse, 'parameter annotations');
} else {
$annotations= null;
}

$line= $parse->token->line;
$type= $this->type($parse);

$parse->forward();
$param= $parse->token->value;
$parse->forward();

if ('=' === $parse->token->value) {
$parse->forward();
$default= $this->expression($parse, 0);
} else {
$default= null;
}

$parameter= new Parameter($param, $type, $default, false, false, null, $annotations, null, $line);
$parse->expecting(')', 'property hook parameters');
} else {
$parameter= null;
}

$line= $parse->token->line;
if ('=>' === $parse->token->value) {
$parse->forward();
$expr= $this->expression($parse, 0);
$parse->expecting(';', 'property hook');
} else if ('{' === $parse->token->value) {
$parse->forward();
$expr= new Block($this->statements($parse), $line);
$parse->expecting('}', 'property hook');
} else {
$parse->expecting(';', 'property hook');
$expr= null;
}

$hooks[$hook]= new Hook($modifiers, $hook, $expr, $byref, $parameter, $line);
}

return $hooks;
}

private function properties($parse, &$body, $meta, $modifiers, $type) {
$comment= $parse->comment;
$parse->comment= null;
Expand Down Expand Up @@ -1374,7 +1445,6 @@ private function properties($parse, &$body, $meta, $modifiers, $type) {

$body[$lookup]= new Property($modifiers, $name, $type, $expr, $annotations, $comment, $line);

// Check for property hooks
if (';' === $parse->token->value) {
$parse->forward();
return;
Expand All @@ -1385,78 +1455,12 @@ private function properties($parse, &$body, $meta, $modifiers, $type) {
$parse->forward();
$expr= $this->expression($parse, 0);
$parse->expecting(';', 'property hook');

$body[$lookup]->hooks['get']= new Hook([], 'get', $expr, false, null, $line);
$body[$lookup]->hooks= ['get' => new Hook([], 'get', $expr, false, null, $line)];
return;
} else if ('{' === $parse->token->value) {
$parse->forward();

while ('}' !== $parse->token->value) {
if ('final' === $parse->token->value) {
$modifiers= ['final'];
$parse->forward();
} else {
$modifiers= [];
}

if ('&' === $parse->token->value) {
$byref= true;
$parse->forward();
} else {
$byref= false;
}

$hook= $parse->token->value;
$parse->forward();

if ('(' === $parse->token->value) {
$parse->forward();

if ('#[' === $parse->token->value) {
$parse->forward();
$annotations= $this->annotations($parse, 'parameter annotations');
} else {
$annotations= null;
}

$line= $parse->token->line;
$type= $this->type($parse);

$parse->forward();
$param= $parse->token->value;
$parse->forward();

if ('=' === $parse->token->value) {
$parse->forward();
$default= $this->expression($parse, 0);
} else {
$default= null;
}

$parameter= new Parameter($param, $type, $default, false, false, null, $annotations, null, $line);
$parse->expecting(')', 'property hook parameters');
} else {
$parameter= null;
}

$line= $parse->token->line;
if ('=>' === $parse->token->value) {
$parse->forward();
$expr= $this->expression($parse, 0);
$parse->expecting(';', 'property hook');
} else if ('{' === $parse->token->value) {
$parse->forward();
$expr= new Block($this->statements($parse), $line);
$parse->expecting('}', 'property hook');
} else if (';' === $parse->token->value) {
$parse->forward();
$expr= null;
}

$body[$lookup]->hooks[$hook]= new Hook($modifiers, $hook, $expr, $byref, $parameter, $line);
}

$parse->forward();
$body[$lookup]->hooks= $this->hooks($parse);
$parse->expecting('}', 'property hook');
return;
} else {
$parse->expecting(';, , or {', 'property');
Expand Down Expand Up @@ -1573,7 +1577,30 @@ private function parameters($parse) {
$parse->forward();
$default= $this->expression($parse, 0);
}
$parameters[]= new Parameter($name, $type, $default, $byref, $variadic, $promote, $annotations, null, $line);

// Only promoted parameters can have hooks
if ($promote) {
$property= new Property($promote, $name, $type, null, null, null, $line);
if ('{' === $parse->token->value) {
$parse->forward();
$property->hooks= $this->hooks($parse);
$parse->expecting('}', 'property hook');
}
} else {
$property= null;
}

$parameters[]= new Parameter(
$name,
$type,
$default,
$byref,
$variadic,
$property,
$annotations,
null,
$line
);

if (')' === $parse->token->value) {
break;
Expand Down
17 changes: 15 additions & 2 deletions src/test/php/lang/ast/unittest/parse/MembersTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,24 @@ public function abstract_property_with_hook() {
#[Test]
public function promoted_parameter() {
$class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE);
$parameter= new Parameter('name', new IsLiteral('string'), null, false, false, ['public'], null, null, self::LINE);
$promoted= new Property(['public'], 'name', new IsLiteral('string'), null, null, null, self::LINE);
$parameter= new Parameter('name', new IsLiteral('string'), null, false, false, $promoted, null, null, self::LINE);
$class->declare(new Method(['public'], '__construct', new Signature([$parameter], null, false, self::LINE), $this->empty, null, null, self::LINE));

$this->assertParsed([$class], 'class A { public function __construct(public string $name) { } }');
}

#[Test]
public function promoted_parameter_with_hook() {
$class= new ClassDeclaration([], new IsValue('\\A'), null, [], [], null, null, self::LINE);
$promoted= new Property(['public'], 'name', new IsLiteral('string'), null, null, null, self::LINE);
$promoted->hooks['get']= new Hook([], 'get', new Literal('"Hello"', self::LINE), false, null, self::LINE);
$parameter= new Parameter('name', new IsLiteral('string'), null, false, false, $promoted, null, null, self::LINE);
$class->declare(new Method(['public'], '__construct', new Signature([$parameter], null, false, self::LINE), $this->empty, null, null, self::LINE));

$this->assertParsed([$class], 'class A { public function __construct(public string $name { get => "Hello"; }) { } }');
}

#[Test]
public function instance_property_access() {
$this->assertParsed(
Expand Down Expand Up @@ -423,7 +435,8 @@ public function asymmetric_property() {

#[Test]
public function asymmetric_property_as_constructor_argument() {
$params= [new Parameter('a', new IsLiteral('int'), null, false, false, ['private(set)'], null, null, self::LINE)];
$promoted= new Property(['private(set)'], 'a', new IsLiteral('int'), null, null, null, self::LINE);
$params= [new Parameter('a', new IsLiteral('int'), null, false, false, $promoted, 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), $this->empty, null, null, self::LINE));

Expand Down
Loading