Skip to content

PHP 8.4 | Tokenizer/PHP: allow for anon class dereferencing #1160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
23 changes: 23 additions & 0 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -3127,6 +3127,29 @@ protected function processAdditional()
$allowed += Tokens::$magicConstants;

for ($x = ($i - 1); $x >= 0; $x--) {
// Allow for PHP 8.4 anon class dereferencing without wrapping parentheses.
// Note: the T_CLASS token has not yet been retokenized to T_ANON_CLASS!
if ($this->tokens[$x]['code'] === T_CLOSE_CURLY_BRACKET
&& isset($this->tokens[$x]['scope_condition']) === true
&& $this->tokens[$this->tokens[$x]['scope_condition']]['code'] === T_CLASS
) {
// Now, make sure it is an anonymous class and not a normal class.
for ($y = ($this->tokens[$x]['scope_condition'] + 1); $y < $numTokens; $y++) {
if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
break;
}
}

// Use the same check as used for the anon class retokenization.
if ($this->tokens[$y]['code'] === T_OPEN_PARENTHESIS
|| $this->tokens[$y]['code'] === T_OPEN_CURLY_BRACKET
|| $this->tokens[$y]['code'] === T_EXTENDS
|| $this->tokens[$y]['code'] === T_IMPLEMENTS
) {
break;
}
}

// If we hit a scope opener, the statement has ended
// without finding anything, so it's probably an array
// using PHP 7.1 short list syntax.
Expand Down
17 changes: 17 additions & 0 deletions tests/Core/Tokenizers/PHP/ShortArrayTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ $var = $obj?->function_call()[$x];
/* testInterpolatedStringDereferencing */
$var = "PHP{$rocks}"[1];

/* testNewAnonClassNoParenthesesExpressionDereferencing */
$a = new class {}[0];

$a = new class (['value'])
/* testNewAnonClassParenthesesExpressionDereferencing */
{}[0];

/* testNewAnonClassExtendsExpressionDereferencing */
$a = new readonly class extends ArrayObject {}[0];

/* testNewAnonClassImplementsExpressionDereferencing */
$a = new class implements ArrayAccess {}[0];

/*
* Short array brackets.
*/
Expand Down Expand Up @@ -106,6 +119,10 @@ if ( true ) :
[ $a ] = [ 'hi' ];
endif;

class Foo extends ArrayObject {}
/* testShortListDeclarationAfterClassDeclaration */
[$a] = ['hi'];

/* testLiveCoding */
// Intentional parse error. This has to be the last test in the file.
$array = [
51 changes: 28 additions & 23 deletions tests/Core/Tokenizers/PHP/ShortArrayTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,29 +55,33 @@ public function testSquareBrackets($testMarker)
public static function dataSquareBrackets()
{
return [
'array access 1' => ['/* testArrayAccess1 */'],
'array access 2' => ['/* testArrayAccess2 */'],
'array assignment' => ['/* testArrayAssignment */'],
'function call dereferencing' => ['/* testFunctionCallDereferencing */'],
'method call dereferencing' => ['/* testMethodCallDereferencing */'],
'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'],
'property dereferencing' => ['/* testPropertyDereferencing */'],
'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'],
'static property dereferencing' => ['/* testStaticPropertyDereferencing */'],
'string dereferencing single quotes' => ['/* testStringDereferencing */'],
'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'],
'global constant dereferencing' => ['/* testConstantDereferencing */'],
'class constant dereferencing' => ['/* testClassConstantDereferencing */'],
'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'],
'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'],
'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'],
'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'],
'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'],
'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'],
'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'],
'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'],
'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'],
'live coding' => ['/* testLiveCoding */'],
'array access 1' => ['/* testArrayAccess1 */'],
'array access 2' => ['/* testArrayAccess2 */'],
'array assignment' => ['/* testArrayAssignment */'],
'function call dereferencing' => ['/* testFunctionCallDereferencing */'],
'method call dereferencing' => ['/* testMethodCallDereferencing */'],
'static method call dereferencing' => ['/* testStaticMethodCallDereferencing */'],
'property dereferencing' => ['/* testPropertyDereferencing */'],
'property dereferencing with inaccessable name' => ['/* testPropertyDereferencingWithInaccessibleName */'],
'static property dereferencing' => ['/* testStaticPropertyDereferencing */'],
'string dereferencing single quotes' => ['/* testStringDereferencing */'],
'string dereferencing double quotes' => ['/* testStringDereferencingDoubleQuoted */'],
'global constant dereferencing' => ['/* testConstantDereferencing */'],
'class constant dereferencing' => ['/* testClassConstantDereferencing */'],
'magic constant dereferencing' => ['/* testMagicConstantDereferencing */'],
'array access with curly braces' => ['/* testArrayAccessCurlyBraces */'],
'array literal dereferencing' => ['/* testArrayLiteralDereferencing */'],
'short array literal dereferencing' => ['/* testShortArrayLiteralDereferencing */'],
'class member dereferencing on instantiation 1' => ['/* testClassMemberDereferencingOnInstantiation1 */'],
'class member dereferencing on instantiation 2' => ['/* testClassMemberDereferencingOnInstantiation2 */'],
'class member dereferencing on clone' => ['/* testClassMemberDereferencingOnClone */'],
'nullsafe method call dereferencing' => ['/* testNullsafeMethodCallDereferencing */'],
'interpolated string dereferencing' => ['/* testInterpolatedStringDereferencing */'],
'new anonymous class expression dereferencing 1' => ['/* testNewAnonClassNoParenthesesExpressionDereferencing */'],
'new anonymous class expression dereferencing 2' => ['/* testNewAnonClassParenthesesExpressionDereferencing */'],
'new anonymous class expression dereferencing 3' => ['/* testNewAnonClassExtendsExpressionDereferencing */'],
'new anonymous class expression dereferencing 4' => ['/* testNewAnonClassImplementsExpressionDereferencing */'],
'live coding' => ['/* testLiveCoding */'],
];

}//end dataSquareBrackets()
Expand Down Expand Up @@ -133,6 +137,7 @@ public static function dataShortArrays()
'short list after braced control structure' => ['/* testShortListDeclarationAfterBracedControlStructure */'],
'short list after non-braced control structure' => ['/* testShortListDeclarationAfterNonBracedControlStructure */'],
'short list after alternative control structure' => ['/* testShortListDeclarationAfterAlternativeControlStructure */'],
'short list after class declaration' => ['/* testShortListDeclarationAfterClassDeclaration */'],
];

}//end dataShortArrays()
Expand Down