Skip to content

Commit 3cf55b7

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 5fd77c7 commit 3cf55b7

File tree

3 files changed

+93
-4
lines changed

3 files changed

+93
-4
lines changed

src/Tokenizers/PHP.php

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3052,19 +3052,46 @@ protected function processAdditional()
30523052
}
30533053

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

3062-
if ($this->tokens[$y]['code'] !== T_PARAM_NAME) {
3063-
$confirmed = true;
3062+
if ($this->tokens[$y]['code'] !== T_CLOSE_PARENTHESIS) {
3063+
// Definitely not a union, intersection or DNF return type, move on.
3064+
continue 2;
3065+
}
3066+
3067+
if (isset($this->tokens[$y]['parenthesis_owner']) === true) {
3068+
if ($this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FUNCTION
3069+
|| $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_CLOSURE
3070+
|| $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_FN
3071+
|| $this->tokens[$this->tokens[$y]['parenthesis_owner']]['code'] === T_USE
3072+
) {
3073+
$confirmed = true;
3074+
}
3075+
3076+
break;
3077+
}
3078+
3079+
// Arrow functions may not have the parenthesis_owner set correctly yet.
3080+
// Closure use tokens won't be parentheses owners until PHPCS 4.0.
3081+
if (isset($this->tokens[$y]['parenthesis_opener']) === true) {
3082+
for ($z = ($this->tokens[$y]['parenthesis_opener'] - 1); $z > 0; $z--) {
3083+
if (isset(Tokens::$emptyTokens[$this->tokens[$z]['code']]) === false) {
3084+
break;
3085+
}
3086+
}
3087+
3088+
if ($this->tokens[$z]['code'] === T_FN) {
3089+
$confirmed = true;
3090+
}
30643091
}
30653092

30663093
break;
3067-
}
3094+
}//end if
30683095

30693096
if ($suspectedType === 'constant' && $this->tokens[$x]['code'] === T_CONST) {
30703097
$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)