Skip to content

Commit be9cbe5

Browse files
TysonAndredantleech
authored andcommitted
Support parsing of PHP 8.4 property hooks
As part of this, stricten the parsing of property names and use a narrower syntax but reuse AssignmentExpression for backwards compatibility of applications that originally used tolerant-php-parser's result of parseExpression. References: https://wiki.php.net/rfc/property-hooks and the linked php-src PR's zend_language_parser.y https://wiki.php.net/rfc/property-hooks#abbreviated_syntax https://wiki.php.net/rfc/property-hooks#interaction_with_constructor_property_promotion
1 parent 0d3aad0 commit be9cbe5

File tree

90 files changed

+2447
-85
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+2447
-85
lines changed

src/Node/Parameter.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ class Parameter extends Node {
3131
public $equalsToken;
3232
/** @var null|Expression */
3333
public $default;
34+
/** @var PropertyHooks|null */
35+
public $propertyHooks;
3436

3537
const CHILD_NAMES = [
3638
'attributes',
@@ -42,7 +44,8 @@ class Parameter extends Node {
4244
'dotDotDotToken',
4345
'variableName',
4446
'equalsToken',
45-
'default'
47+
'default',
48+
'propertyHooks',
4649
];
4750

4851
public function isVariadic() {

src/Node/PropertyDeclaration.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Microsoft\PhpParser\ModifiedTypeInterface;
1111
use Microsoft\PhpParser\ModifiedTypeTrait;
1212
use Microsoft\PhpParser\Node;
13+
use Microsoft\PhpParser\Node\PropertyHooks;
1314
use Microsoft\PhpParser\Node\DelimitedList\QualifiedNameList;
1415
use Microsoft\PhpParser\Token;
1516

@@ -28,6 +29,9 @@ class PropertyDeclaration extends Node implements ModifiedTypeInterface {
2829
/** @var DelimitedList\ExpressionList */
2930
public $propertyElements;
3031

32+
/** @var PropertyHooks|null */
33+
public $propertyHooks;
34+
3135
/** @var Token */
3236
public $semicolon;
3337

@@ -37,6 +41,7 @@ class PropertyDeclaration extends Node implements ModifiedTypeInterface {
3741
'questionToken',
3842
'typeDeclarationList',
3943
'propertyElements',
44+
'propertyHooks',
4045
'semicolon'
4146
];
4247
}

src/Node/PropertyHook.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node;
8+
9+
use Microsoft\PhpParser\Node;
10+
use Microsoft\PhpParser\Node\Statement\CompoundStatementNode;
11+
use Microsoft\PhpParser\Token;
12+
13+
class PropertyHook extends Node {
14+
/** @var AttributeGroup[]|null */
15+
public $attributes;
16+
/** @var Token */
17+
public $byRefToken;
18+
/** @var Token */
19+
public $name;
20+
/** @var Token|null */
21+
public $openParen;
22+
/** @var DelimitedList\ParameterDeclarationList|null */
23+
public $parameters;
24+
/** @var Token|null */
25+
public $closeParen;
26+
/** @var Token|null */
27+
public $arrowToken;
28+
/** @var CompoundStatementNode|Expression|Token|null */
29+
public $body;
30+
/** @var Token|null */
31+
public $semicolon;
32+
33+
const CHILD_NAMES = [
34+
'attributes',
35+
'byRefToken',
36+
'name',
37+
'openParen',
38+
'parameters',
39+
'closeParen',
40+
'arrowToken',
41+
'body',
42+
'semicolon',
43+
];
44+
}

src/Node/PropertyHooks.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
/*---------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All rights reserved.
4+
* Licensed under the MIT License. See License.txt in the project root for license information.
5+
*--------------------------------------------------------------------------------------------*/
6+
7+
namespace Microsoft\PhpParser\Node;
8+
9+
use Microsoft\PhpParser\Node;
10+
use Microsoft\PhpParser\Node\PropertyHooks;
11+
use Microsoft\PhpParser\Token;
12+
13+
class PropertyHooks extends Node {
14+
/** @var Token */
15+
public $openBrace;
16+
17+
/** @var PropertyHook[] */
18+
public $hookDeclarations;
19+
20+
/** @var Token */
21+
public $closeBrace;
22+
23+
const CHILD_NAMES = [
24+
'openBrace',
25+
'hookDeclarations',
26+
'closeBrace'
27+
];
28+
}

src/Parser.php

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@
7474
use Microsoft\PhpParser\Node\NumericLiteral;
7575
use Microsoft\PhpParser\Node\ParenthesizedIntersectionType;
7676
use Microsoft\PhpParser\Node\PropertyDeclaration;
77+
use Microsoft\PhpParser\Node\PropertyHooks;
78+
use Microsoft\PhpParser\Node\PropertyHook;
7779
use Microsoft\PhpParser\Node\ReservedWord;
7880
use Microsoft\PhpParser\Node\StringLiteral;
7981
use Microsoft\PhpParser\Node\MethodDeclaration;
@@ -877,6 +879,10 @@ private function parseParameterFn() {
877879
// TODO add post-parse rule that checks for invalid assignments
878880
$parameter->default = $this->parseExpression($parameter);
879881
}
882+
883+
if ($this->token->kind === TokenKind::OpenBraceToken) {
884+
$parameter->propertyHooks = $this->parsePropertyHooks($parameter);
885+
}
880886
return $parameter;
881887
};
882888
}
@@ -3437,12 +3443,115 @@ private function parsePropertyDeclaration($parentNode, $modifiers, $questionToke
34373443
} elseif ($questionToken) {
34383444
$propertyDeclaration->typeDeclarationList = new MissingToken(TokenKind::PropertyType, $this->token->fullStart);
34393445
}
3440-
$propertyDeclaration->propertyElements = $this->parseExpressionList($propertyDeclaration);
3441-
$propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);
3446+
$propertyDeclaration->propertyElements = $this->parsePropertyNameList($propertyDeclaration);
3447+
if ($this->token->kind === TokenKind::OpenBraceToken) {
3448+
$propertyDeclaration->propertyHooks = $this->parsePropertyHooks($propertyDeclaration);
3449+
} else {
3450+
$propertyDeclaration->semicolon = $this->eat1(TokenKind::SemicolonToken);
3451+
}
34423452

34433453
return $propertyDeclaration;
34443454
}
34453455

3456+
/**
3457+
* @param PropertyDeclaration $parentNode
3458+
* @return DelimitedList\VariableNameList
3459+
*/
3460+
private function parsePropertyNameList($parentNode) {
3461+
// XXX this used to be implemented with parseExpressionList so keep the same classes.
3462+
return $this->parseDelimitedList(
3463+
DelimitedList\ExpressionList::class,
3464+
TokenKind::CommaToken,
3465+
function ($token) {
3466+
return $token->kind === TokenKind::VariableName;
3467+
},
3468+
$this->parsePropertyVariableNameAndDefault(),
3469+
$parentNode
3470+
);
3471+
}
3472+
3473+
private function parsePropertyVariableNameAndDefault() {
3474+
return function ($parentNode) {
3475+
// Imitate the format that parseExpression would have returned from when
3476+
// parseExpression was originally used.
3477+
// This is more precise and rules out parse errors such as `public $propName + 2;`
3478+
// This approach also avoids conflict with the deprecated $x{expr} array access syntax.
3479+
$variable = new Variable();
3480+
$variable->name = $this->eat1(TokenKind::VariableName);
3481+
$equalsToken = $this->eatOptional1(TokenKind::EqualsToken);
3482+
if ($equalsToken === null) {
3483+
$variable->parent = $parentNode;
3484+
return $variable;
3485+
}
3486+
3487+
$byRefToken = $this->eatOptional1(TokenKind::AmpersandToken); // byRef default is nonsense, but this is a compile-time error instead of a parse error.
3488+
$rightOperand = $this->parseExpression($parentNode); // gets overridden in makeBinaryExpression
3489+
return $this->makeBinaryExpression($variable, $equalsToken, $byRefToken, $rightOperand, $parentNode);
3490+
};
3491+
}
3492+
3493+
/**
3494+
* @param PropertyDeclaration|Parameter $parent
3495+
* @return PropertyHooks
3496+
*/
3497+
private function parsePropertyHooks(Node $parent) {
3498+
$propertyHooks = new PropertyHooks();
3499+
$propertyHooks->parent = $parent;
3500+
$propertyHooks->openBrace = $this->eat1(TokenKind::OpenBraceToken);
3501+
$hooks = [];
3502+
while (in_array($this->getCurrentToken()->kind, self::PROPERTY_HOOK_START_TOKENS, true)) {
3503+
$hooks[] = $this->parsePropertyHook($propertyHooks);
3504+
}
3505+
$propertyHooks->hookDeclarations = $hooks;
3506+
$propertyHooks->closeBrace = $this->eat1(TokenKind::CloseBraceToken);
3507+
return $propertyHooks;
3508+
}
3509+
3510+
const PROPERTY_HOOK_START_TOKENS = [
3511+
TokenKind::Name,
3512+
TokenKind::AmpersandToken, // by reference
3513+
TokenKind::AttributeToken,
3514+
];
3515+
3516+
private function isPropertyHookStart() {
3517+
return function ($token) {
3518+
return \in_array($token->kind, self::PROPERTY_HOOK_START_TOKENS, true);
3519+
};
3520+
}
3521+
3522+
/**
3523+
* @param PropertyHooks $parentNode
3524+
* @return PropertyHook
3525+
*/
3526+
private function parsePropertyHook($parentNode) {
3527+
$node = new PropertyHook();
3528+
$node->parent = $parentNode;
3529+
if ($this->getCurrentToken()->kind === TokenKind::AttributeToken) {
3530+
$node->attributes = $this->parseAttributeGroups($node);
3531+
}
3532+
$node->byRefToken = $this->eatOptional1(TokenKind::AmpersandToken);
3533+
$node->name = $this->eat1(TokenKind::Name); // "get" or "set" - other values are compile errors, not parse errors.
3534+
$node->openParen = $this->eatOptional1(TokenKind::OpenParenToken);
3535+
if ($node->openParen) {
3536+
$node->parameters = $this->parseDelimitedList(
3537+
DelimitedList\ParameterDeclarationList::class,
3538+
TokenKind::CommaToken,
3539+
$this->isParameterStartFn(),
3540+
$this->parseParameterFn(),
3541+
$node);
3542+
$node->closeParen = $this->eat1(TokenKind::CloseParenToken);
3543+
}
3544+
// e.g. `get => expr;` or `get { return $expr; }`
3545+
$node->arrowToken = $this->eatOptional1(TokenKind::DoubleArrowToken);
3546+
if ($node->arrowToken) {
3547+
$node->body = $this->parseExpression($node);
3548+
$node->semicolon = $this->eat1(TokenKind::SemicolonToken);
3549+
} else {
3550+
$node->body = $this->parseCompoundStatement($node);
3551+
}
3552+
return $node;
3553+
}
3554+
34463555
/**
34473556
* Parse a comma separated qualified name list (e.g. interfaces implemented by a class)
34483557
*

tests/cases/parser/classMethods3.php.tree

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
"textLength": 8
7373
},
7474
"equalsToken": null,
75-
"default": null
75+
"default": null,
76+
"propertyHooks": null
7677
}
7778
},
7879
{
@@ -93,7 +94,8 @@
9394
"textLength": 6
9495
},
9596
"equalsToken": null,
96-
"default": null
97+
"default": null,
98+
"propertyHooks": null
9799
}
98100
}
99101
]

tests/cases/parser/classMethods4.php.tree

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
"textLength": 8
7373
},
7474
"equalsToken": null,
75-
"default": null
75+
"default": null,
76+
"propertyHooks": null
7677
}
7778
},
7879
{
@@ -93,7 +94,8 @@
9394
"textLength": 6
9495
},
9596
"equalsToken": null,
96-
"default": null
97+
"default": null,
98+
"propertyHooks": null
9799
}
98100
}
99101
]

tests/cases/parser/dnfTypesParameter1.php.tree

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@
111111
"textLength": 2
112112
},
113113
"equalsToken": null,
114-
"default": null
114+
"default": null,
115+
"propertyHooks": null
115116
}
116117
}
117118
]

tests/cases/parser/dnfTypesParameter2.php.tree

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@
100100
"textLength": 2
101101
},
102102
"equalsToken": null,
103-
"default": null
103+
"default": null,
104+
"propertyHooks": null
104105
}
105106
}
106107
]

tests/cases/parser/dnfTypesParameter3.php.tree

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@
7171
"textLength": 2
7272
},
7373
"equalsToken": null,
74-
"default": null
74+
"default": null,
75+
"propertyHooks": null
7576
}
7677
}
7778
]

0 commit comments

Comments
 (0)