From 8505acd1512d5ab4f096d01a03685468d243c5f9 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Sat, 19 Sep 2020 15:47:14 +0200 Subject: [PATCH] Correctly handle ?-> in encapsed strings Followup upstream change. --- .../TokenEmulator/NullsafeTokenEmulator.php | 36 ++++++++++++++----- test/PhpParser/Lexer/EmulativeTest.php | 16 +++++++++ test/code/parser/expr/nullsafe.test | 12 ++++--- test/code/prettyPrinter/expr/nullsafe.test | 2 +- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php b/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php index 686f305..1a29c67 100644 --- a/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php +++ b/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php @@ -22,15 +22,35 @@ final class NullsafeTokenEmulator extends TokenEmulator // the tokens array on the way $line = 1; for ($i = 0, $c = count($tokens); $i < $c; ++$i) { - if (isset($tokens[$i + 1])) { - if ($tokens[$i] === '?' && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) { - array_splice($tokens, $i, 2, [ - [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line] - ]); - $c--; - continue; - } + if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) { + array_splice($tokens, $i, 2, [ + [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line] + ]); + $c--; + continue; } + + // Handle ?-> inside encapsed string. + if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1]) + && $tokens[$i - 1][0] === \T_VARIABLE + && preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches) + ) { + $replacement = [ + [\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line], + [\T_STRING, $matches[1], $line], + ]; + if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) { + $replacement[] = [ + \T_ENCAPSED_AND_WHITESPACE, + \substr($tokens[$i][1], \strlen($matches[0])), + $line + ]; + } + array_splice($tokens, $i, 1, $replacement); + $c += \count($replacement) - 1; + continue; + } + if (\is_array($tokens[$i])) { $line += substr_count($tokens[$i][1], "\n"); } diff --git a/test/PhpParser/Lexer/EmulativeTest.php b/test/PhpParser/Lexer/EmulativeTest.php index 1aec595..d32bb1c 100644 --- a/test/PhpParser/Lexer/EmulativeTest.php +++ b/test/PhpParser/Lexer/EmulativeTest.php @@ -320,6 +320,22 @@ class EmulativeTest extends LexerTest ['7.4', 'match', [[Tokens::T_STRING, 'match']]], ['7.4', 'fn', [[Tokens::T_FN, 'fn']]], ['7.3', 'fn', [[Tokens::T_STRING, 'fn']]], + // Tested here to skip testLeaveStuffAloneInStrings. + ['8.0', '"$foo?->bar"', [ + [ord('"'), '"'], + [Tokens::T_VARIABLE, '$foo'], + [Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'], + [Tokens::T_STRING, 'bar'], + [ord('"'), '"'], + ]], + ['8.0', '"$foo?->bar baz"', [ + [ord('"'), '"'], + [Tokens::T_VARIABLE, '$foo'], + [Tokens::T_NULLSAFE_OBJECT_OPERATOR, '?->'], + [Tokens::T_STRING, 'bar'], + [Tokens::T_ENCAPSED_AND_WHITESPACE, ' baz'], + [ord('"'), '"'], + ]], ]; } } diff --git a/test/code/parser/expr/nullsafe.test b/test/code/parser/expr/nullsafe.test index ebff2ba..270663a 100644 --- a/test/code/parser/expr/nullsafe.test +++ b/test/code/parser/expr/nullsafe.test @@ -71,11 +71,13 @@ array( 4: Stmt_Expression( expr: Scalar_Encapsed( parts: array( - 0: Expr_Variable( - name: a - ) - 1: Scalar_EncapsedStringPart( - value: ?->b + 0: Expr_NullsafePropertyFetch( + var: Expr_Variable( + name: a + ) + name: Identifier( + name: b + ) ) ) ) diff --git a/test/code/prettyPrinter/expr/nullsafe.test b/test/code/prettyPrinter/expr/nullsafe.test index 6f5ec32..de9adc9 100644 --- a/test/code/prettyPrinter/expr/nullsafe.test +++ b/test/code/prettyPrinter/expr/nullsafe.test @@ -19,4 +19,4 @@ $a?->b($c)?->d; $a?->b($c)(); new $a?->b(); "{$a?->b}"; -"{$a}?->b"; \ No newline at end of file +"{$a?->b}"; \ No newline at end of file