Skip to content

Commit 38c01b3

Browse files
committed
PHP 8.0 | File::getMethodProperties(): add support for "union" return types
This adds support for union types to the `File::getMethodProperties()` method. Includes extensive unit tests.
1 parent 564b780 commit 38c01b3

File tree

3 files changed

+299
-0
lines changed

3 files changed

+299
-0
lines changed

src/Files/File.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,8 +1634,11 @@ public function getMethodProperties($stackPtr)
16341634
T_SELF => T_SELF,
16351635
T_PARENT => T_PARENT,
16361636
T_STATIC => T_STATIC,
1637+
T_FALSE => T_FALSE,
1638+
T_NULL => T_NULL,
16371639
T_NAMESPACE => T_NAMESPACE,
16381640
T_NS_SEPARATOR => T_NS_SEPARATOR,
1641+
T_TYPE_UNION => T_TYPE_UNION,
16391642
];
16401643

16411644
for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) {

tests/Core/File/GetMethodPropertiesTest.inc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,46 @@ function mixedTypeHintNullable(): ?mixed {}
8383

8484
/* testNamespaceOperatorTypeHint */
8585
function namespaceOperatorTypeHint() : ?namespace\Name {}
86+
87+
/* testPHP8UnionTypesSimple */
88+
function unionTypeSimple($number) : int|float {}
89+
90+
/* testPHP8UnionTypesTwoClasses */
91+
$fn = fn($var): MyClassA|\Package\MyClassB => $var;
92+
93+
/* testPHP8UnionTypesAllBaseTypes */
94+
function unionTypesAllBaseTypes() : array|bool|callable|int|float|null|Object|string {}
95+
96+
/* testPHP8UnionTypesAllPseudoTypes */
97+
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
98+
function unionTypesAllPseudoTypes($var) : false|MIXED|self|parent|static|iterable|Resource|void {}
99+
100+
/* testPHP8UnionTypesNullable */
101+
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
102+
$closure = function () use($a) :?int|float {};
103+
104+
/* testPHP8PseudoTypeNull */
105+
// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
106+
function pseudoTypeNull(): null {}
107+
108+
/* testPHP8PseudoTypeFalse */
109+
// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
110+
function pseudoTypeFalse(): false {}
111+
112+
/* testPHP8PseudoTypeFalseAndBool */
113+
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
114+
function pseudoTypeFalseAndBool(): bool|false {}
115+
116+
/* testPHP8ObjectAndClass */
117+
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
118+
function objectAndClass(): object|ClassName {}
119+
120+
/* testPHP8PseudoTypeIterableAndArray */
121+
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
122+
interface FooBar {
123+
public function pseudoTypeIterableAndArray(): iterable|array|Traversable;
124+
}
125+
126+
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
127+
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
128+
function duplicateTypeInUnion(): int | /*comment*/ string | INT {}

tests/Core/File/GetMethodPropertiesTest.php

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,259 @@ public function testNamespaceOperatorTypeHint()
475475
}//end testNamespaceOperatorTypeHint()
476476

477477

478+
/**
479+
* Verify recognition of PHP8 union type declaration.
480+
*
481+
* @return void
482+
*/
483+
public function testPHP8UnionTypesSimple()
484+
{
485+
$expected = [
486+
'scope' => 'public',
487+
'scope_specified' => false,
488+
'return_type' => 'int|float',
489+
'nullable_return_type' => false,
490+
'is_abstract' => false,
491+
'is_final' => false,
492+
'is_static' => false,
493+
'has_body' => true,
494+
];
495+
496+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
497+
498+
}//end testPHP8UnionTypesSimple()
499+
500+
501+
/**
502+
* Verify recognition of PHP8 union type declaration with two classes.
503+
*
504+
* @return void
505+
*/
506+
public function testPHP8UnionTypesTwoClasses()
507+
{
508+
$expected = [
509+
'scope' => 'public',
510+
'scope_specified' => false,
511+
'return_type' => 'MyClassA|\Package\MyClassB',
512+
'nullable_return_type' => false,
513+
'is_abstract' => false,
514+
'is_final' => false,
515+
'is_static' => false,
516+
'has_body' => true,
517+
];
518+
519+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
520+
521+
}//end testPHP8UnionTypesTwoClasses()
522+
523+
524+
/**
525+
* Verify recognition of PHP8 union type declaration with all base types.
526+
*
527+
* @return void
528+
*/
529+
public function testPHP8UnionTypesAllBaseTypes()
530+
{
531+
$expected = [
532+
'scope' => 'public',
533+
'scope_specified' => false,
534+
'return_type' => 'array|bool|callable|int|float|null|Object|string',
535+
'nullable_return_type' => false,
536+
'is_abstract' => false,
537+
'is_final' => false,
538+
'is_static' => false,
539+
'has_body' => true,
540+
];
541+
542+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
543+
544+
}//end testPHP8UnionTypesAllBaseTypes()
545+
546+
547+
/**
548+
* Verify recognition of PHP8 union type declaration with all pseudo types.
549+
*
550+
* @return void
551+
*/
552+
public function testPHP8UnionTypesAllPseudoTypes()
553+
{
554+
$expected = [
555+
'scope' => 'public',
556+
'scope_specified' => false,
557+
'return_type' => 'false|MIXED|self|parent|static|iterable|Resource|void',
558+
'nullable_return_type' => false,
559+
'is_abstract' => false,
560+
'is_final' => false,
561+
'is_static' => false,
562+
'has_body' => true,
563+
];
564+
565+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
566+
567+
}//end testPHP8UnionTypesAllPseudoTypes()
568+
569+
570+
/**
571+
* Verify recognition of PHP8 union type declaration with (illegal) nullability.
572+
*
573+
* @return void
574+
*/
575+
public function testPHP8UnionTypesNullable()
576+
{
577+
$expected = [
578+
'scope' => 'public',
579+
'scope_specified' => false,
580+
'return_type' => '?int|float',
581+
'nullable_return_type' => true,
582+
'is_abstract' => false,
583+
'is_final' => false,
584+
'is_static' => false,
585+
'has_body' => true,
586+
];
587+
588+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
589+
590+
}//end testPHP8UnionTypesNullable()
591+
592+
593+
/**
594+
* Verify recognition of PHP8 type declaration with (illegal) single type null.
595+
*
596+
* @return void
597+
*/
598+
public function testPHP8PseudoTypeNull()
599+
{
600+
$expected = [
601+
'scope' => 'public',
602+
'scope_specified' => false,
603+
'return_type' => 'null',
604+
'nullable_return_type' => false,
605+
'is_abstract' => false,
606+
'is_final' => false,
607+
'is_static' => false,
608+
'has_body' => true,
609+
];
610+
611+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
612+
613+
}//end testPHP8PseudoTypeNull()
614+
615+
616+
/**
617+
* Verify recognition of PHP8 type declaration with (illegal) single type false.
618+
*
619+
* @return void
620+
*/
621+
public function testPHP8PseudoTypeFalse()
622+
{
623+
$expected = [
624+
'scope' => 'public',
625+
'scope_specified' => false,
626+
'return_type' => 'false',
627+
'nullable_return_type' => false,
628+
'is_abstract' => false,
629+
'is_final' => false,
630+
'is_static' => false,
631+
'has_body' => true,
632+
];
633+
634+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
635+
636+
}//end testPHP8PseudoTypeFalse()
637+
638+
639+
/**
640+
* Verify recognition of PHP8 type declaration with (illegal) type false combined with type bool.
641+
*
642+
* @return void
643+
*/
644+
public function testPHP8PseudoTypeFalseAndBool()
645+
{
646+
$expected = [
647+
'scope' => 'public',
648+
'scope_specified' => false,
649+
'return_type' => 'bool|false',
650+
'nullable_return_type' => false,
651+
'is_abstract' => false,
652+
'is_final' => false,
653+
'is_static' => false,
654+
'has_body' => true,
655+
];
656+
657+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
658+
659+
}//end testPHP8PseudoTypeFalseAndBool()
660+
661+
662+
/**
663+
* Verify recognition of PHP8 type declaration with (illegal) type object combined with a class name.
664+
*
665+
* @return void
666+
*/
667+
public function testPHP8ObjectAndClass()
668+
{
669+
$expected = [
670+
'scope' => 'public',
671+
'scope_specified' => false,
672+
'return_type' => 'object|ClassName',
673+
'nullable_return_type' => false,
674+
'is_abstract' => false,
675+
'is_final' => false,
676+
'is_static' => false,
677+
'has_body' => true,
678+
];
679+
680+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
681+
682+
}//end testPHP8ObjectAndClass()
683+
684+
685+
/**
686+
* Verify recognition of PHP8 type declaration with (illegal) type iterable combined with array/Traversable.
687+
*
688+
* @return void
689+
*/
690+
public function testPHP8PseudoTypeIterableAndArray()
691+
{
692+
$expected = [
693+
'scope' => 'public',
694+
'scope_specified' => true,
695+
'return_type' => 'iterable|array|Traversable',
696+
'nullable_return_type' => false,
697+
'is_abstract' => false,
698+
'is_final' => false,
699+
'is_static' => false,
700+
'has_body' => false,
701+
];
702+
703+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
704+
705+
}//end testPHP8PseudoTypeIterableAndArray()
706+
707+
708+
/**
709+
* Verify recognition of PHP8 type declaration with (illegal) duplicate types.
710+
*
711+
* @return void
712+
*/
713+
public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
714+
{
715+
$expected = [
716+
'scope' => 'public',
717+
'scope_specified' => false,
718+
'return_type' => 'int|string|INT',
719+
'nullable_return_type' => false,
720+
'is_abstract' => false,
721+
'is_final' => false,
722+
'is_static' => false,
723+
'has_body' => true,
724+
];
725+
726+
$this->getMethodPropertiesTestHelper('/* '.__FUNCTION__.' */', $expected);
727+
728+
}//end testPHP8DuplicateTypeInUnionWhitespaceAndComment()
729+
730+
478731
/**
479732
* Test helper.
480733
*

0 commit comments

Comments
 (0)