Skip to content

Commit 0c1f4d5

Browse files
committed
Tokenizer/PHP: fix handling of "DNF look a likes" with switch/case and 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
1 parent 9e60f9f commit 0c1f4d5

File tree

3 files changed

+92
-4
lines changed

3 files changed

+92
-4
lines changed

src/Tokenizers/PHP.php

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3226,19 +3226,45 @@ protected function processAdditional()
32263226
}
32273227

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

3236-
if ($this->tokens[$y]['code'] !== T_PARAM_NAME) {
3237-
$confirmed = true;
3236+
if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
3237+
// Definitely not a union, intersection or DNF return type, move on.
3238+
continue 2;
3239+
}
3240+
3241+
if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
3242+
if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
3243+
|| $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
3244+
|| $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
3245+
) {
3246+
$confirmed = true;
3247+
}
3248+
3249+
break;
3250+
}
3251+
3252+
// Arrow functions may not have the parenthesis_owner set correctly yet.
3253+
// Closure use tokens won't be parentheses owners until PHPCS 4.0.
3254+
if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
3255+
for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
3256+
if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
3257+
break;
3258+
}
3259+
}
3260+
3261+
if ($this->tokens[$z]['code'] === T_FN || $this->tokens[$z]['code'] === T_USE) {
3262+
$confirmed = true;
3263+
}
32383264
}
32393265

32403266
break;
3241-
}
3267+
}//end if
32423268

32433269
if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
32443270
$confirmed = true;

tests/Core/Tokenizer/PHP/DNFTypesTest.inc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,35 @@ callMe(label: CONST_A | CONST_B);
5353
/* testParensNoOwnerFunctionCallWithDNFLookALikeNamedParamIntersect */
5454
callMe(label: CONST_A & CONST_B);
5555

56+
/* testSwitchControlStructureCondition */
57+
switch (CONST_A | CONST_B) {
58+
/* testFunctionCallInSwitchCaseCondition */
59+
case get_bool():
60+
/* testFunctionCallInSwitchCaseBody */
61+
\Name\functionInSwitch();
62+
break;
63+
64+
default:
65+
/* testFunctionCallInSwitchDefaultBody */
66+
functionInSwitch();
67+
break;
68+
}
69+
70+
/* testIfAlternativeSyntaxCondition */
71+
if (true):
72+
/* testFunctionCallInIfBody */
73+
\B\call();
74+
/* testElseIfAlternativeSyntaxCondition */
75+
elseif (10):
76+
/* testFunctionCallInElseIfBody */
77+
C\call();
78+
endif;
79+
80+
gotolabel:
81+
/* testFunctionCallInGotoBody */
82+
\doSomething();
83+
84+
5685
/*
5786
* DNF parentheses.
5887
*/
@@ -165,6 +194,8 @@ $closureWithParamType = function ( /* testDNFTypeClosureParamIllegalNullable */
165194
/* testParensOwnerClosureAmpersandInDefaultValue */
166195
$closureWithReturnType = function ($string = NONSENSE & FAKE) /* testDNFTypeClosureReturn */ : (\Package\MyA&PackageB)|null {};
167196

197+
$closureWithUseAndReturnType = function ($foo) use ($a) /* testDNFTypeClosureWithUseReturn */ : null|(Foo&\Bar)|false {};
198+
168199
/* testParensOwnerArrowDNFUsedWithin */
169200
$arrowWithParamType = fn (
170201
/* testDNFTypeArrowParam */

tests/Core/Tokenizer/PHP/DNFTypesTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,33 @@ public static function dataNormalParentheses()
193193
'parens without owner in arrow function return expression' => [
194194
'testMarker' => '/* testParensNoOwnerInArrowReturnExpression */',
195195
],
196+
'parens with owner: switch condition' => [
197+
'testMarker' => '/* testSwitchControlStructureCondition */',
198+
],
199+
'parens without owner in switch-case condition' => [
200+
'testMarker' => '/* testFunctionCallInSwitchCaseCondition */',
201+
],
202+
'parens without owner in switch-case body' => [
203+
'testMarker' => '/* testFunctionCallInSwitchCaseBody */',
204+
],
205+
'parens without owner in switch-default body' => [
206+
'testMarker' => '/* testFunctionCallInSwitchDefaultBody */',
207+
],
208+
'parens with owner: if condition, alternative syntax' => [
209+
'testMarker' => '/* testIfAlternativeSyntaxCondition */',
210+
],
211+
'parens without owner in if body, alternative syntax' => [
212+
'testMarker' => '/* testFunctionCallInIfBody */',
213+
],
214+
'parens with owner: elseif condition, alternative syntax' => [
215+
'testMarker' => '/* testElseIfAlternativeSyntaxCondition */',
216+
],
217+
'parens without owner in elseif body, alternative syntax' => [
218+
'testMarker' => '/* testFunctionCallInElseIfBody */',
219+
],
220+
'parens without owner in goto body' => [
221+
'testMarker' => '/* testFunctionCallInGotoBody */',
222+
],
196223
];
197224

198225
}//end dataNormalParentheses()
@@ -423,6 +450,10 @@ public static function dataDNFTypeParentheses()
423450
'closure return type' => [
424451
'testMarker' => '/* testDNFTypeClosureReturn */',
425452
],
453+
'closure with use return type' => [
454+
'testMarker' => '/* testDNFTypeClosureWithUseReturn */',
455+
],
456+
426457
'arrow function param type' => [
427458
'testMarker' => '/* testDNFTypeArrowParam */',
428459
],

0 commit comments

Comments
 (0)