Skip to content

Commit

Permalink
Tokenizer/PHP: fix handling of "DNF look a likes" with switch/case an…
Browse files Browse the repository at this point in the history
…d alternative control structure syntax

Follow up on 461, 507, 508

This commit intends to further improve the determination of whether tokens are part of a (return) type declaration which need special handling for the tokens or not.

It does more strenuous checking on any `T_COLON` encountered before the (potential) type, to try and determine with higher accuracy whether the colon indicates the start of a return type - in contrast to being a colon for a switch-case or a colon for an alternative syntax control structure.

Includes tests.
The tests also include a test with `goto`. This would currently not trigger this code as the colon is tokenized as part of the goto label, but as the intention is to change that tokenization in PHPCS 4.0 (per issue 185), the test being there should safeguard that the PHPCS 4.0 change will not cause any problems with the DNF related tokenization.

For now, this fixes the bug(s) reported in issue 630, though I think it might be good to revisit the tokenizer control flow in a future major, but as that will be so much easier once JS/CSS no longer needs to be taken into account, that's for the future.

Fixes 630
  • Loading branch information
jrfnl committed Oct 16, 2024
1 parent 9e60f9f commit 0c1f4d5
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 4 deletions.
34 changes: 30 additions & 4 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -3226,19 +3226,45 @@ protected function processAdditional()
}

if ($suspectedType === 'return' && $this->tokens[$x]['code'] === T_COLON) {
// Make sure this is not the colon from a parameter name.
// Make sure this is the colon for a return type.
for ($y = ($x - 1); $y > 0; $y--) {
if (isset(Tokens::$emptyTokens[$this->tokens[$y]['code']]) === false) {
break;
}
}

if ($this->tokens[$y]['code'] !== T_PARAM_NAME) {
$confirmed = true;
if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
// Definitely not a union, intersection or DNF return type, move on.
continue 2;
}

if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
|| $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
|| $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
) {
$confirmed = true;
}

break;
}

// Arrow functions may not have the parenthesis_owner set correctly yet.
// Closure use tokens won't be parentheses owners until PHPCS 4.0.
if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
break;
}
}

if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
$confirmed = true;
}
}

break;
}
}//end if

if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
$confirmed = true;
Expand Down
31 changes: 31 additions & 0 deletions tests/Core/Tokenizer/PHP/DNFTypesTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,35 @@ callMe(label: CONST_A | CONST_B);
/* testParensNoOwnerFunctionCallWithDNFLookALikeNamedParamIntersect */
callMe(label: CONST_A & CONST_B);

/* testSwitchControlStructureCondition */
switch (CONST_A | CONST_B) {
/* testFunctionCallInSwitchCaseCondition */
case get_bool():
/* testFunctionCallInSwitchCaseBody */
\Name\functionInSwitch();
break;

default:
/* testFunctionCallInSwitchDefaultBody */
functionInSwitch();
break;
}

/* testIfAlternativeSyntaxCondition */
if (true):
/* testFunctionCallInIfBody */
\B\call();
/* testElseIfAlternativeSyntaxCondition */
elseif (10):
/* testFunctionCallInElseIfBody */
C\call();
endif;

gotolabel:
/* testFunctionCallInGotoBody */
\doSomething();


/*
* DNF parentheses.
*/
Expand Down Expand Up @@ -165,6 +194,8 @@ $closureWithParamType = function ( /* testDNFTypeClosureParamIllegalNullable */
/* testParensOwnerClosureAmpersandInDefaultValue */
$closureWithReturnType = function ($string = NONSENSE & FAKE) /* testDNFTypeClosureReturn */ : (\Package\MyA&PackageB)|null {};

$closureWithUseAndReturnType = function ($foo) use ($a) /* testDNFTypeClosureWithUseReturn */ : null|(Foo&\Bar)|false {};

/* testParensOwnerArrowDNFUsedWithin */
$arrowWithParamType = fn (
/* testDNFTypeArrowParam */
Expand Down
31 changes: 31 additions & 0 deletions tests/Core/Tokenizer/PHP/DNFTypesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,33 @@ public static function dataNormalParentheses()
'parens without owner in arrow function return expression' => [
'testMarker' => '/* testParensNoOwnerInArrowReturnExpression */',
],
'parens with owner: switch condition' => [
'testMarker' => '/* testSwitchControlStructureCondition */',
],
'parens without owner in switch-case condition' => [
'testMarker' => '/* testFunctionCallInSwitchCaseCondition */',
],
'parens without owner in switch-case body' => [
'testMarker' => '/* testFunctionCallInSwitchCaseBody */',
],
'parens without owner in switch-default body' => [
'testMarker' => '/* testFunctionCallInSwitchDefaultBody */',
],
'parens with owner: if condition, alternative syntax' => [
'testMarker' => '/* testIfAlternativeSyntaxCondition */',
],
'parens without owner in if body, alternative syntax' => [
'testMarker' => '/* testFunctionCallInIfBody */',
],
'parens with owner: elseif condition, alternative syntax' => [
'testMarker' => '/* testElseIfAlternativeSyntaxCondition */',
],
'parens without owner in elseif body, alternative syntax' => [
'testMarker' => '/* testFunctionCallInElseIfBody */',
],
'parens without owner in goto body' => [
'testMarker' => '/* testFunctionCallInGotoBody */',
],
];

}//end dataNormalParentheses()
Expand Down Expand Up @@ -423,6 +450,10 @@ public static function dataDNFTypeParentheses()
'closure return type' => [
'testMarker' => '/* testDNFTypeClosureReturn */',
],
'closure with use return type' => [
'testMarker' => '/* testDNFTypeClosureWithUseReturn */',
],

'arrow function param type' => [
'testMarker' => '/* testDNFTypeArrowParam */',
],
Expand Down

0 comments on commit 0c1f4d5

Please sign in to comment.