Skip to content

Commit dc6fe75

Browse files
committed
PHP 8.0 | Tokenizer/PHP: arrow function backfill vs PHP8 union types
As the `PHP::processAdditional()` method walks backwards through the file, by the time the `fn` keyword backfill logic is hit, any union type `|` tokens will have already been converted to `T_TYPE_UNION`. So, to make the arrow function backfill compatible with PHP 8 union types, the `T_TYPE_UNION` token needs to be added to the "allowed tokens" (`$ignore`) array. Includes unit tests.
1 parent 8112f56 commit dc6fe75

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

src/Tokenizers/PHP.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,7 @@ protected function processAdditional()
19991999
T_PARENT => T_PARENT,
20002000
T_SELF => T_SELF,
20012001
T_STATIC => T_STATIC,
2002+
T_TYPE_UNION => T_TYPE_UNION,
20022003
];
20032004

20042005
$closer = $this->tokens[$x]['parenthesis_closer'];

tests/Core/Tokenizer/BackfillFnTokenTest.inc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ fn(array $a) : array => $a;
8181
/* testStaticReturnType */
8282
fn(array $a) : static => $a;
8383

84+
/* testUnionParamType */
85+
$arrowWithUnionParam = fn(int|float $param) : SomeClass => new SomeClass($param);
86+
87+
/* testUnionReturnType */
88+
$arrowWithUnionReturn = fn($param) : int|float => $param | 10;
89+
8490
/* testTernary */
8591
$fn = fn($a) => $a ? /* testTernaryThen */ fn() : string => 'a' : /* testTernaryElse */ fn() : string => 'b';
8692

@@ -130,6 +136,9 @@ $a = MyNS\Sub\Fn($param);
130136
/* testNonArrowNamespaceOperatorFunctionCall */
131137
$a = namespace\fn($param);
132138

139+
/* testNonArrowFunctionNameWithUnionTypes */
140+
function fn(int|float $param) : string|null {}
141+
133142
/* testLiveCoding */
134143
// Intentional parse error. This has to be the last test in the file.
135144
$fn = fn

tests/Core/Tokenizer/BackfillFnTokenTest.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,62 @@ public function testKeywordReturnTypes()
531531
}//end testKeywordReturnTypes()
532532

533533

534+
/**
535+
* Test arrow function with a union parameter type.
536+
*
537+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
538+
*
539+
* @return void
540+
*/
541+
public function testUnionParamType()
542+
{
543+
$tokens = self::$phpcsFile->getTokens();
544+
545+
$token = $this->getTargetToken('/* testUnionParamType */', T_FN);
546+
$this->backfillHelper($token);
547+
548+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 13), 'Scope opener is not the arrow token');
549+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 21), 'Scope closer is not the semicolon token');
550+
551+
$opener = $tokens[$token]['scope_opener'];
552+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 13), 'Opener scope opener is not the arrow token');
553+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 21), 'Opener scope closer is not the semicolon token');
554+
555+
$closer = $tokens[$token]['scope_closer'];
556+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 13), 'Closer scope opener is not the arrow token');
557+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 21), 'Closer scope closer is not the semicolon token');
558+
559+
}//end testUnionParamType()
560+
561+
562+
/**
563+
* Test arrow function with a union return type.
564+
*
565+
* @covers PHP_CodeSniffer\Tokenizers\PHP::processAdditional
566+
*
567+
* @return void
568+
*/
569+
public function testUnionReturnType()
570+
{
571+
$tokens = self::$phpcsFile->getTokens();
572+
573+
$token = $this->getTargetToken('/* testUnionReturnType */', T_FN);
574+
$this->backfillHelper($token);
575+
576+
$this->assertSame($tokens[$token]['scope_opener'], ($token + 11), 'Scope opener is not the arrow token');
577+
$this->assertSame($tokens[$token]['scope_closer'], ($token + 18), 'Scope closer is not the semicolon token');
578+
579+
$opener = $tokens[$token]['scope_opener'];
580+
$this->assertSame($tokens[$opener]['scope_opener'], ($token + 11), 'Opener scope opener is not the arrow token');
581+
$this->assertSame($tokens[$opener]['scope_closer'], ($token + 18), 'Opener scope closer is not the semicolon token');
582+
583+
$closer = $tokens[$token]['scope_closer'];
584+
$this->assertSame($tokens[$closer]['scope_opener'], ($token + 11), 'Closer scope opener is not the arrow token');
585+
$this->assertSame($tokens[$closer]['scope_closer'], ($token + 18), 'Closer scope closer is not the semicolon token');
586+
587+
}//end testUnionReturnType()
588+
589+
534590
/**
535591
* Test arrow functions used in ternary operators.
536592
*
@@ -690,6 +746,7 @@ public function dataNotAnArrowFunction()
690746
'Fn',
691747
],
692748
['/* testNonArrowNamespaceOperatorFunctionCall */'],
749+
['/* testNonArrowFunctionNameWithUnionTypes */'],
693750
['/* testLiveCoding */'],
694751
];
695752

0 commit comments

Comments
 (0)