Skip to content

Commit 603d9c6

Browse files
committed
Tokenizer: improve tokenization of T_NULLABLE vs T_INLINE_THEN
This is an attempt to make the differentiation between T_NULLABLE and T_INLINE_THEN more stable. There is only a limited set of tokens which can be used for type declarations. If a `?` is not followed by one of these, we can be certain that it is a ternary operator `T_INLINE_THEN`. The more resource intensive "walking back" check will now only be done when it could be either. The "walking back" token logic could probably still do with an additional check for `T_INSTANCEOF`, but the changes now made, should prevent the majority of issues where `T_INLINE_THEN` is misidentified as `T_NULLABLE`. Includes unit tests via the PSR12.Functions.NullableTypeDeclaration sniff.
1 parent 65e88a3 commit 603d9c6

File tree

3 files changed

+83
-0
lines changed

3 files changed

+83
-0
lines changed

src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,19 @@ class TestTokenizingOfNullableVsInlineThen {
6666
}
6767
}
6868

69+
// Issue #2641.
6970
$foo = new static(
7071
is_null($a) ? foo($a) : $a,
7172
is_null($b) ? $b : $c
7273
);
74+
75+
// Issue #2791.
76+
class testInstanceOf() {
77+
function testIt() {
78+
$foo = $value instanceof static ? '(' . $value . ')' : $value;
79+
$bar = $value instanceof static ? function_call($value) : $value;
80+
$baz = $value instanceof static ? array($value) : $value;
81+
$bal = $value instanceof static ? \className::$property : $value;
82+
$bal = $value instanceof static ? CONSTANT_NAME : $value;
83+
}
84+
}

src/Standards/PSR12/Tests/Functions/NullableTypeDeclarationUnitTest.inc.fixed

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,19 @@ class TestTokenizingOfNullableVsInlineThen {
6464
}
6565
}
6666

67+
// Issue #2641.
6768
$foo = new static(
6869
is_null($a) ? foo($a) : $a,
6970
is_null($b) ? $b : $c
7071
);
72+
73+
// Issue #2791.
74+
class testInstanceOf() {
75+
function testIt() {
76+
$foo = $value instanceof static ? '(' . $value . ')' : $value;
77+
$bar = $value instanceof static ? function_call($value) : $value;
78+
$baz = $value instanceof static ? array($value) : $value;
79+
$bal = $value instanceof static ? \className::$property : $value;
80+
$bal = $value instanceof static ? CONSTANT_NAME : $value;
81+
}
82+
}

src/Tokenizers/PHP.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,65 @@ protected function tokenize($string)
10381038
$newToken = [];
10391039
$newToken['content'] = '?';
10401040

1041+
/*
1042+
* Check if the next non-empty token is one of the tokens which can be used
1043+
* in type declarations. If not, it's definitely a ternary.
1044+
* At this point, the only token types which need to be taken into consideration
1045+
* as potential type declarations are T_STRING, T_ARRAY, T_CALLABLE and T_NS_SEPARATOR.
1046+
*/
1047+
1048+
$lastRelevantNonEmpty = null;
1049+
1050+
for ($i = ($stackPtr + 1); $i < $numTokens; $i++) {
1051+
if (is_array($tokens[$i]) === true) {
1052+
$tokenType = $tokens[$i][0];
1053+
} else {
1054+
$tokenType = $tokens[$i];
1055+
}
1056+
1057+
if (isset(Util\Tokens::$emptyTokens[$tokenType]) === true) {
1058+
continue;
1059+
}
1060+
1061+
if ($tokenType === T_STRING
1062+
|| $tokenType === T_ARRAY
1063+
|| $tokenType === T_NS_SEPARATOR
1064+
) {
1065+
$lastRelevantNonEmpty = $tokenType;
1066+
continue;
1067+
}
1068+
1069+
if (($tokenType !== T_CALLABLE
1070+
&& isset($lastRelevantNonEmpty) === false)
1071+
|| ($lastRelevantNonEmpty === T_ARRAY
1072+
&& $tokenType === '(')
1073+
|| ($lastRelevantNonEmpty === T_STRING
1074+
&& ($tokenType === T_DOUBLE_COLON
1075+
|| $tokenType === '('
1076+
|| $tokenType === ':'))
1077+
) {
1078+
if (PHP_CODESNIFFER_VERBOSITY > 1) {
1079+
echo "\t\t* token $stackPtr changed from ? to T_INLINE_THEN".PHP_EOL;
1080+
}
1081+
1082+
$newToken['code'] = T_INLINE_THEN;
1083+
$newToken['type'] = 'T_INLINE_THEN';
1084+
1085+
$insideInlineIf[] = $stackPtr;
1086+
1087+
$finalTokens[$newStackPtr] = $newToken;
1088+
$newStackPtr++;
1089+
continue 2;
1090+
}
1091+
1092+
break;
1093+
}//end for
1094+
1095+
/*
1096+
* This can still be a nullable type or a ternary.
1097+
* Do additional checking.
1098+
*/
1099+
10411100
$prevNonEmpty = null;
10421101
$lastSeenNonEmpty = null;
10431102

0 commit comments

Comments
 (0)