Skip to content

PHP 8.4 | Tokenizer/PHP: fix union/intersection/DNF type tokenization with abstract properties #1183

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 1 commit into from
Aug 12, 2025
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
3 changes: 2 additions & 1 deletion src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -3499,7 +3499,8 @@ protected function processAdditional()
|| $this->tokens[$x]['code'] === T_VAR
|| $this->tokens[$x]['code'] === T_STATIC
|| $this->tokens[$x]['code'] === T_READONLY
|| $this->tokens[$x]['code'] === T_FINAL)
|| $this->tokens[$x]['code'] === T_FINAL
|| $this->tokens[$x]['code'] === T_ABSTRACT)
) {
// This will also confirm constructor property promotion parameters, but that's fine.
$confirmed = true;
Expand Down
11 changes: 10 additions & 1 deletion tests/Core/Tokenizers/PHP/BitwiseOrTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/* testBitwiseOr1 */
$result = $value | $test /* testBitwiseOr2 */ | $another;

class TypeUnion
abstract class TypeUnion
{
/* testTypeUnionOOConstSimple */
public const Foo|Bar SIMPLE = new Foo;
Expand Down Expand Up @@ -96,6 +96,15 @@ class TypeUnion
/* testTypeUnionPropertyPublicProtected */
public protected(set) Foo|Bar $asym4;

/* testTypeUnionWithPHP84AbstractKeyword */
abstract int|string $abstractKeywordA { get; }

/* testTypeUnionWithPHP84AbstractKeywordFirst */
abstract protected float|null $abstractKeywordB { set; }

/* testTypeUnionWithPHP84AbstractKeywordAndFQN */
abstract \MyClass|false $abstractKeywordC { get; }

public function paramTypes(
/* testTypeUnionParam1 */
int|float $paramA /* testBitwiseOrParamDefaultValue */ = CONSTANT_A | CONSTANT_B,
Expand Down
3 changes: 3 additions & 0 deletions tests/Core/Tokenizers/PHP/BitwiseOrTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ public static function dataTypeUnion()
'type for public private(set) property' => ['/* testTypeUnionPropertyPublicPrivateSet */'],
'type for protected(set) property' => ['/* testTypeUnionPropertyProtected */'],
'type for public protected(set) property' => ['/* testTypeUnionPropertyPublicProtected */'],
'type for abstract property, no visibility' => ['/* testTypeUnionWithPHP84AbstractKeyword */'],
'type for abstract property, reversed modifier order' => ['/* testTypeUnionWithPHP84AbstractKeywordFirst */'],
'type for abstract property, no visibility, FQN type' => ['/* testTypeUnionWithPHP84AbstractKeywordAndFQN */'],
'type for method parameter' => ['/* testTypeUnionParam1 */'],
'type for method parameter, first in multi-union' => ['/* testTypeUnionParam2 */'],
'type for method parameter, last in multi-union' => ['/* testTypeUnionParam3 */'],
Expand Down
6 changes: 6 additions & 0 deletions tests/Core/Tokenizers/PHP/DNFTypesTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ abstract class DNFTypes {
/* testDNFTypePropertyWithPublicProtectedSet */
public protected(set) (A&B&C)|true $asym4;

/* testDNFTypeWithPHP84AbstractKeyword */
abstract (className&InterfaceName)|false $abstractKeywordA { set; }

/* testDNFTypeWithPHP84AbstractKeywordAndPublic */
abstract public (\className&\InterfaceName)|false $abstractKeywordB { get; }

public function paramTypes(
/* testDNFTypeParam1WithAttribute */
#[MyAttribute]
Expand Down
7 changes: 7 additions & 0 deletions tests/Core/Tokenizers/PHP/DNFTypesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,13 @@ public static function dataDNFTypeParentheses()
'OO property type: asymmetric vis, public protected(set)' => [
'testMarker' => '/* testDNFTypePropertyWithPublicProtectedSet */',
],
'OO property type: with only abstract keyword' => [
'testMarker' => '/* testDNFTypeWithPHP84AbstractKeyword */',
],
'OO property type: with abstract and public keyword' => [
'testMarker' => '/* testDNFTypeWithPHP84AbstractKeywordAndPublic */',
],

'OO method param type: first param' => [
'testMarker' => '/* testDNFTypeParam1WithAttribute */',
],
Expand Down
8 changes: 7 additions & 1 deletion tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.inc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

class Nullable
abstract class Nullable
{
/* testNullableReadonlyOnly */
readonly ?int $prop;
Expand All @@ -10,6 +10,12 @@ class Nullable

/* testNullablePublicProtectedSet */
public protected(set) ?int $prop3;

/* testNullableFinalOnly */
final ?bool $prop4;

/* testNullableAbstractOnly */
abstract ?string $prop5 { get; }
}

class InlineThen
Expand Down
4 changes: 3 additions & 1 deletion tests/Core/Tokenizers/PHP/NullableVsInlineThenTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,15 @@ public static function dataNullable()
'property declaration, readonly, no visibility' => ['/* testNullableReadonlyOnly */'],
'property declaration, private set' => ['/* testNullablePrivateSet */'],
'property declaration, public and protected set' => ['/* testNullablePublicProtectedSet */'],
'property declaration, final, no visibility' => ['/* testNullableFinalOnly */'],
'property declaration, abstract, no visibility' => ['/* testNullableAbstractOnly */'],
];

}//end dataNullable()


/**
* Test that "readonly" when not used as the keyword is still tokenized as `T_STRING`.
* Test that a "?" when used as part of a ternary expression is tokenized as `T_INLINE_THEN`.
*
* @param string $testMarker The comment which prefaces the target token in the test file.
*
Expand Down
8 changes: 7 additions & 1 deletion tests/Core/Tokenizers/PHP/TypeIntersectionTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/* testBitwiseAnd1 */
$result = $value & $test /* testBitwiseAnd2 */ & $another;

class TypeIntersection
abstract class TypeIntersection
{
/* testTypeIntersectionOOConstSimple */
public const Foo&Bar SIMPLE = new Foo();
Expand Down Expand Up @@ -81,6 +81,12 @@ class TypeIntersection
/* testTypeIntersectionPropertyWithPublicProtectedSet */
public protected(set) Foo&Bar $asym4;

/* testTypeIntersectionWithPHP84AbstractKeyword */
abstract className&InterfaceName $abstractKeywordA { get; }

/* testTypeIntersectionWithPHP84AbstractKeywordFirst */
abstract protected \className&InterfaceName $abstractKeywordB { set; }

public function paramTypes(
/* testTypeIntersectionParam1 */
Foo&Bar $paramA /* testBitwiseAndParamDefaultValue */ = CONSTANT_A & CONSTANT_B,
Expand Down
2 changes: 2 additions & 0 deletions tests/Core/Tokenizers/PHP/TypeIntersectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ public static function dataTypeIntersection()
'type for asymmetric visibility (public private(set)) prop' => ['/* testTypeIntersectionPropertyWithPublicPrivateSet */'],
'type for asymmetric visibility (protected(set)) property' => ['/* testTypeIntersectionPropertyWithProtectedSet */'],
'type for asymmetric visibility (public protected(set)) prop' => ['/* testTypeIntersectionPropertyWithPublicProtectedSet */'],
'type for abstract property' => ['/* testTypeIntersectionWithPHP84AbstractKeyword */'],
'type for abstract property reversed modifier order' => ['/* testTypeIntersectionWithPHP84AbstractKeywordFirst */'],
'type for method parameter' => ['/* testTypeIntersectionParam1 */'],
'type for method parameter, first in multi-intersect' => ['/* testTypeIntersectionParam2 */'],
'type for method parameter, last in multi-intersect' => ['/* testTypeIntersectionParam3 */'],
Expand Down
Loading