Skip to content

Commit 6478c5a

Browse files
committed
Fix PropertyHook::getStmts() for set hook
Produce the correct desugaring if the propertyName attribute is set, and set it in the parser. Otherwise throw an exception. Fixes nikic#1053.
1 parent 8bb4159 commit 6478c5a

File tree

6 files changed

+76
-11
lines changed

6 files changed

+76
-11
lines changed

grammar/php.y

+6-3
Original file line numberDiff line numberDiff line change
@@ -686,11 +686,13 @@ parameter:
686686
optional_attributes optional_property_modifiers optional_type_without_static
687687
optional_arg_ref optional_ellipsis plain_variable optional_property_hook_list
688688
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1, $7);
689-
$this->checkParam($$); }
689+
$this->checkParam($$);
690+
$this->addPropertyNameToHooks($$); }
690691
| optional_attributes optional_property_modifiers optional_type_without_static
691692
optional_arg_ref optional_ellipsis plain_variable '=' expr optional_property_hook_list
692693
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1, $9);
693-
$this->checkParam($$); }
694+
$this->checkParam($$);
695+
$this->addPropertyNameToHooks($$); }
694696
| optional_attributes optional_property_modifiers optional_type_without_static
695697
optional_arg_ref optional_ellipsis error
696698
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
@@ -841,7 +843,8 @@ class_statement:
841843
| optional_attributes variable_modifiers optional_type_without_static property_declaration_list '{' property_hook_list '}'
842844
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1, $6);
843845
$this->checkPropertyHooksForMultiProperty($$, #5);
844-
$this->checkEmptyPropertyHookList($6, #5); }
846+
$this->checkEmptyPropertyHookList($6, #5);
847+
$this->addPropertyNameToHooks($$); }
845848
#endif
846849
| optional_attributes method_modifiers T_CONST class_const_list semi
847850
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);

lib/PhpParser/Node/PropertyHook.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace PhpParser\Node;
44

55
use PhpParser\Modifiers;
6+
use PhpParser\Node\Expr\Assign;
7+
use PhpParser\Node\Expr\PropertyFetch;
8+
use PhpParser\Node\Expr\Variable;
69
use PhpParser\Node\Stmt\Expression;
710
use PhpParser\Node\Stmt\Return_;
811
use PhpParser\NodeAbstract;
@@ -74,8 +77,14 @@ public function getStmts(): ?array {
7477
return [new Return_($this->body)];
7578
}
7679
if ($name === 'set') {
77-
// TODO: This should generate $this->prop = $expr, but we don't know the property name.
78-
return [new Expression($this->body)];
80+
if (!$this->hasAttribute('propertyName')) {
81+
throw new \LogicException(
82+
'Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
83+
}
84+
85+
$propName = $this->getAttribute('propertyName');
86+
$prop = new PropertyFetch(new Variable('this'), (string) $propName);
87+
return [new Expression(new Assign($prop, $this->body))];
7988
}
8089
throw new \LogicException('Unknown property hook "' . $name . '"');
8190
}

lib/PhpParser/Parser/Php7.php

+2
Original file line numberDiff line numberDiff line change
@@ -1844,10 +1844,12 @@ protected function initReduceCallbacks(): void {
18441844
290 => static function ($self, $stackPos) {
18451845
$self->semValue = new Node\Param($self->semStack[$stackPos-(7-6)], null, $self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-4)], $self->semStack[$stackPos-(7-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-1)], $self->semStack[$stackPos-(7-7)]);
18461846
$self->checkParam($self->semValue);
1847+
$self->addPropertyNameToHooks($self->semValue);
18471848
},
18481849
291 => static function ($self, $stackPos) {
18491850
$self->semValue = new Node\Param($self->semStack[$stackPos-(9-6)], $self->semStack[$stackPos-(9-8)], $self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-4)], $self->semStack[$stackPos-(9-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(9-2)], $self->semStack[$stackPos-(9-1)], $self->semStack[$stackPos-(9-9)]);
18501851
$self->checkParam($self->semValue);
1852+
$self->addPropertyNameToHooks($self->semValue);
18511853
},
18521854
292 => static function ($self, $stackPos) {
18531855
$self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]);

lib/PhpParser/Parser/Php8.php

+3
Original file line numberDiff line numberDiff line change
@@ -1839,10 +1839,12 @@ protected function initReduceCallbacks(): void {
18391839
290 => static function ($self, $stackPos) {
18401840
$self->semValue = new Node\Param($self->semStack[$stackPos-(7-6)], null, $self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-4)], $self->semStack[$stackPos-(7-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-1)], $self->semStack[$stackPos-(7-7)]);
18411841
$self->checkParam($self->semValue);
1842+
$self->addPropertyNameToHooks($self->semValue);
18421843
},
18431844
291 => static function ($self, $stackPos) {
18441845
$self->semValue = new Node\Param($self->semStack[$stackPos-(9-6)], $self->semStack[$stackPos-(9-8)], $self->semStack[$stackPos-(9-3)], $self->semStack[$stackPos-(9-4)], $self->semStack[$stackPos-(9-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(9-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(9-2)], $self->semStack[$stackPos-(9-1)], $self->semStack[$stackPos-(9-9)]);
18451846
$self->checkParam($self->semValue);
1847+
$self->addPropertyNameToHooks($self->semValue);
18461848
},
18471849
292 => static function ($self, $stackPos) {
18481850
$self->semValue = new Node\Param(new Expr\Error($self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos])), null, $self->semStack[$stackPos-(6-3)], $self->semStack[$stackPos-(6-4)], $self->semStack[$stackPos-(6-5)], $self->getAttributes($self->tokenStartStack[$stackPos-(6-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(6-2)], $self->semStack[$stackPos-(6-1)]);
@@ -1995,6 +1997,7 @@ protected function initReduceCallbacks(): void {
19951997
$self->semValue = new Stmt\Property($self->semStack[$stackPos-(7-2)], $self->semStack[$stackPos-(7-4)], $self->getAttributes($self->tokenStartStack[$stackPos-(7-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(7-3)], $self->semStack[$stackPos-(7-1)], $self->semStack[$stackPos-(7-6)]);
19961998
$self->checkPropertyHooksForMultiProperty($self->semValue, $stackPos-(7-5));
19971999
$self->checkEmptyPropertyHookList($self->semStack[$stackPos-(7-6)], $stackPos-(7-5));
2000+
$self->addPropertyNameToHooks($self->semValue);
19982001
},
19992002
349 => static function ($self, $stackPos) {
20002003
$self->semValue = new Stmt\ClassConst($self->semStack[$stackPos-(5-4)], $self->semStack[$stackPos-(5-2)], $self->getAttributes($self->tokenStartStack[$stackPos-(5-1)], $self->tokenEndStack[$stackPos]), $self->semStack[$stackPos-(5-1)]);

lib/PhpParser/ParserAbstract.php

+15
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
use PhpParser\Node\Stmt\Property;
3333
use PhpParser\Node\Stmt\TryCatch;
3434
use PhpParser\Node\UseItem;
35+
use PhpParser\Node\VarLikeIdentifier;
3536
use PhpParser\NodeVisitor\CommentAnnotatingVisitor;
3637

3738
abstract class ParserAbstract implements Parser {
@@ -1201,6 +1202,20 @@ protected function checkPropertyHookModifiers(int $a, int $b, int $modifierPos):
12011202
}
12021203
}
12031204

1205+
/**
1206+
* @param Property|Param $node
1207+
*/
1208+
protected function addPropertyNameToHooks(Node $node): void {
1209+
if ($node instanceof Property) {
1210+
$name = $node->props[0]->name->toString();
1211+
} else {
1212+
$name = $node->var->name;
1213+
}
1214+
foreach ($node->hooks as $hook) {
1215+
$hook->setAttribute('propertyName', $name);
1216+
}
1217+
}
1218+
12041219
/** @param array<Node\Arg|Node\VariadicPlaceholder> $args */
12051220
private function isSimpleExit(array $args): bool {
12061221
if (\count($args) === 0) {

test/PhpParser/Node/PropertyHookTest.php

+39-6
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
namespace PhpParser\Node;
44

55
use PhpParser\Modifiers;
6+
use PhpParser\Node\Expr\Assign;
7+
use PhpParser\Node\Expr\PropertyFetch;
68
use PhpParser\Node\Expr\Variable;
9+
use PhpParser\Node\Scalar\Int_;
710
use PhpParser\Node\Stmt\Expression;
811
use PhpParser\Node\Stmt\Return_;
12+
use PhpParser\ParserFactory;
13+
use PhpParser\PrettyPrinter\Standard;
914

1015
class PropertyHookTest extends \PHPUnit\Framework\TestCase {
1116
/**
@@ -40,17 +45,45 @@ public function testGetStmts(): void {
4045
$get = new PropertyHook('get', $expr);
4146
$this->assertEquals([new Return_($expr)], $get->getStmts());
4247

43-
// TODO: This is incorrect.
44-
$set = new PropertyHook('set', $expr);
45-
$this->assertEquals([new Expression($expr)], $set->getStmts());
48+
$set = new PropertyHook('set', $expr, [], ['propertyName' => 'abc']);
49+
$this->assertEquals([
50+
new Expression(new Assign(new PropertyFetch(new Variable('this'), 'abc'), $expr))
51+
], $set->getStmts());
52+
}
53+
54+
public function testGetStmtsSetHookFromParser(): void {
55+
$parser = (new ParserFactory())->createForNewestSupportedVersion();
56+
$prettyPrinter = new Standard();
57+
$stmts = $parser->parse(<<<'CODE'
58+
<?php
59+
class Test {
60+
public $prop1 { set => 123; }
61+
62+
public function __construct(public $prop2 { set => 456; }) {}
63+
}
64+
CODE);
65+
66+
$hook1 = $stmts[0]->stmts[0]->hooks[0];
67+
$this->assertEquals('$this->prop1 = 123;', $prettyPrinter->prettyPrint($hook1->getStmts()));
68+
69+
$hook2 = $stmts[0]->stmts[1]->params[0]->hooks[0];
70+
$this->assertEquals('$this->prop2 = 456;', $prettyPrinter->prettyPrint($hook2->getStmts()));
4671
}
4772

48-
public function testSetStmtsUnknownHook(): void {
73+
public function testGetStmtsUnknownHook(): void {
4974
$expr = new Variable('test');
50-
$get = new PropertyHook('foobar', $expr);
75+
$hook = new PropertyHook('foobar', $expr);
5176

5277
$this->expectException(\LogicException::class);
5378
$this->expectExceptionMessage('Unknown property hook "foobar"');
54-
$get->getStmts();
79+
$hook->getStmts();
80+
}
81+
82+
public function testGetStmtsSetHookWithoutPropertyName(): void {
83+
$expr = new Variable('test');
84+
$set = new PropertyHook('set', $expr);
85+
$this->expectException(\LogicException::class);
86+
$this->expectExceptionMessage('Can only use getStmts() on a "set" hook if the "propertyName" attribute is set');
87+
$set->getStmts();
5588
}
5689
}

0 commit comments

Comments
 (0)