From b4ad1ad8fa39fdbea7a47e7977e9f8672640cdf7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 25 Mar 2021 14:01:42 +0100 Subject: [PATCH] PHP 8.0 | Tokenizer/PHP: efficiency tweaks This PR contains two separate, small efficiency tweaks for the `match` expression retokenization. 1. The tokenizer was walking backwards in the `match` backfill, while it could have just used `$finalTokens[$lastNotEmptyToken]` to get the last non-empty token. 2. There was some redundancy in the tokens which were being checked for the token "before". For tokens like, `T_CLASS` and `T_CONST`, the next token after the `match` keyword could never be an open parenthesis, so they would already be marked as "not match". Includes additional tests. Additionally, when I initially started the work on backfilling `match` expressions, the nullsafe object operator had not yet been backfilled, so there was not test covering that case. Added now just to be on the safe side. Note: all extra tests would already pass prior to this change. These are just extra safeguards. --- src/Tokenizers/PHP.php | 39 +++++++------------ .../Core/Tokenizer/BackfillMatchTokenTest.inc | 14 ++++++- .../Core/Tokenizer/BackfillMatchTokenTest.php | 12 ++++++ 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/Tokenizers/PHP.php b/src/Tokenizers/PHP.php index 06263166e9..0b7ebd2976 100644 --- a/src/Tokenizers/PHP.php +++ b/src/Tokenizers/PHP.php @@ -1288,33 +1288,22 @@ protected function tokenize($string) break; } - // Next was an open parenthesis, now check what is before the match keyword. - for ($y = ($stackPtr - 1); $y >= 0; $y--) { - if (is_array($tokens[$y]) === true - && isset(Util\Tokens::$emptyTokens[$tokens[$y][0]]) === true - ) { - continue; - } + $notMatchContext = [ + T_PAAMAYIM_NEKUDOTAYIM => true, + T_OBJECT_OPERATOR => true, + T_NULLSAFE_OBJECT_OPERATOR => true, + T_NS_SEPARATOR => true, + T_NEW => true, + T_FUNCTION => true, + ]; - if (is_array($tokens[$y]) === true - && ($tokens[$y][0] === T_PAAMAYIM_NEKUDOTAYIM - || $tokens[$y][0] === T_OBJECT_OPERATOR - || $tokens[$y][0] === T_NS_SEPARATOR - || $tokens[$y][0] === T_NEW - || $tokens[$y][0] === T_FUNCTION - || $tokens[$y][0] === T_CLASS - || $tokens[$y][0] === T_INTERFACE - || $tokens[$y][0] === T_TRAIT - || $tokens[$y][0] === T_NAMESPACE - || $tokens[$y][0] === T_CONST) - ) { - // This is not a match expression. - break 2; - } + if (isset($notMatchContext[$finalTokens[$lastNotEmptyToken]['code']]) === true) { + // Also not a match expression. + break; + } - $isMatch = true; - break 2; - }//end for + $isMatch = true; + break; }//end for if ($isMatch === true && $token[0] === T_STRING) { diff --git a/tests/Core/Tokenizer/BackfillMatchTokenTest.inc b/tests/Core/Tokenizer/BackfillMatchTokenTest.inc index 3edc9cd5b6..095a4d7dfc 100644 --- a/tests/Core/Tokenizer/BackfillMatchTokenTest.inc +++ b/tests/Core/Tokenizer/BackfillMatchTokenTest.inc @@ -220,7 +220,7 @@ $a = MyClass::Match[$a]; $a = $obj->match($param); /* testNoMatchMethodCallUpper */ -$a = $obj->MATCH()->chain($param); +$a = $obj??->MATCH()->chain($param); /* testNoMatchPropertyAccess */ $a = $obj->match; @@ -282,6 +282,18 @@ function match() {} // Intentional fatal error. Match is now a reserved keyword. namespace Match {} +/* testNoMatchExtendedClassDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +class Foo extends Match {} + +/* testNoMatchImplementedClassDeclaration */ +// Intentional fatal error. Match is now a reserved keyword. +class Bar implements Match {} + +/* testNoMatchInUseStatement */ +// Intentional fatal error in PHP < 8. Match is now a reserved keyword. +use Match\me; + function brokenMatchNoCurlies($x) { /* testNoMatchMissingCurlies */ // Intentional fatal error. New control structure is not supported without curly braces. diff --git a/tests/Core/Tokenizer/BackfillMatchTokenTest.php b/tests/Core/Tokenizer/BackfillMatchTokenTest.php index c9f7fad89d..80f909acdf 100644 --- a/tests/Core/Tokenizer/BackfillMatchTokenTest.php +++ b/tests/Core/Tokenizer/BackfillMatchTokenTest.php @@ -296,6 +296,18 @@ public function dataNotAMatchStructure() '/* testNoMatchNamespaceDeclaration */', 'Match', ], + 'class_extends_declaration' => [ + '/* testNoMatchExtendedClassDeclaration */', + 'Match', + ], + 'class_implements_declaration' => [ + '/* testNoMatchImplementedClassDeclaration */', + 'Match', + ], + 'use_statement' => [ + '/* testNoMatchInUseStatement */', + 'Match', + ], 'unsupported_inline_control_structure' => ['/* testNoMatchMissingCurlies */'], 'unsupported_alternative_syntax' => ['/* testNoMatchAlternativeSyntax */'], 'live_coding' => ['/* testLiveCoding */'],