Skip to content

Commit 564b780

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

File tree

3 files changed

+324
-1
lines changed

3 files changed

+324
-1
lines changed

src/Files/File.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,9 @@ public function getMethodParameters($stackPtr)
14431443
break;
14441444
case T_NAMESPACE:
14451445
case T_NS_SEPARATOR:
1446+
case T_TYPE_UNION:
1447+
case T_FALSE:
1448+
case T_NULL:
14461449
// Part of a type hint or default value.
14471450
if ($defaultStart === null) {
14481451
if ($typeHintToken === false) {

tests/Core/File/GetMethodParametersTest.inc

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,47 @@ function mixedTypeHintNullable(?Mixed $var1) {}
4141

4242
/* testNamespaceOperatorTypeHint */
4343
function namespaceOperatorTypeHint(?namespace\Name $var1) {}
44+
45+
/* testPHP8UnionTypesSimple */
46+
function unionTypeSimple(int|float $number, self|parent &...$obj) {}
47+
48+
/* testPHP8UnionTypesSimpleWithBitwiseOrInDefault */
49+
$fn = fn(int|float $var = CONSTANT_A | CONSTANT_B) => $var;
50+
51+
/* testPHP8UnionTypesTwoClasses */
52+
function unionTypesTwoClasses(MyClassA|\Package\MyClassB $var) {}
53+
54+
/* testPHP8UnionTypesAllBaseTypes */
55+
function unionTypesAllBaseTypes(array|bool|callable|int|float|null|object|string $var) {}
56+
57+
/* testPHP8UnionTypesAllPseudoTypes */
58+
// Intentional fatal error - mixing types which cannot be combined, but that's not the concern of the method.
59+
function unionTypesAllPseudoTypes(false|mixed|self|parent|iterable|Resource $var) {}
60+
61+
/* testPHP8UnionTypesNullable */
62+
// Intentional fatal error - nullability is not allowed with union types, but that's not the concern of the method.
63+
$closure = function (?int|float $number) {};
64+
65+
/* testPHP8PseudoTypeNull */
66+
// Intentional fatal error - null pseudotype is only allowed in union types, but that's not the concern of the method.
67+
function pseudoTypeNull(null $var = null) {}
68+
69+
/* testPHP8PseudoTypeFalse */
70+
// Intentional fatal error - false pseudotype is only allowed in union types, but that's not the concern of the method.
71+
function pseudoTypeFalse(false $var = false) {}
72+
73+
/* testPHP8PseudoTypeFalseAndBool */
74+
// Intentional fatal error - false pseudotype is not allowed in combination with bool, but that's not the concern of the method.
75+
function pseudoTypeFalseAndBool(bool|false $var = false) {}
76+
77+
/* testPHP8ObjectAndClass */
78+
// Intentional fatal error - object is not allowed in combination with class name, but that's not the concern of the method.
79+
function objectAndClass(object|ClassName $var) {}
80+
81+
/* testPHP8PseudoTypeIterableAndArray */
82+
// Intentional fatal error - iterable pseudotype is not allowed in combination with array or Traversable, but that's not the concern of the method.
83+
function pseudoTypeIterableAndArray(iterable|array|Traversable $var) {}
84+
85+
/* testPHP8DuplicateTypeInUnionWhitespaceAndComment */
86+
// Intentional fatal error - duplicate types are not allowed in union types, but that's not the concern of the method.
87+
function duplicateTypeInUnion( int | string /*comment*/ | INT $var) {}

tests/Core/File/GetMethodParametersTest.php

Lines changed: 277 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,282 @@ public function testNamespaceOperatorTypeHint()
340340
}//end testNamespaceOperatorTypeHint()
341341

342342

343+
/**
344+
* Verify recognition of PHP8 union type declaration.
345+
*
346+
* @return void
347+
*/
348+
public function testPHP8UnionTypesSimple()
349+
{
350+
$expected = [];
351+
$expected[0] = [
352+
'name' => '$number',
353+
'content' => 'int|float $number',
354+
'pass_by_reference' => false,
355+
'variable_length' => false,
356+
'type_hint' => 'int|float',
357+
'nullable_type' => false,
358+
];
359+
$expected[1] = [
360+
'name' => '$obj',
361+
'content' => 'self|parent &...$obj',
362+
'pass_by_reference' => true,
363+
'variable_length' => true,
364+
'type_hint' => 'self|parent',
365+
'nullable_type' => false,
366+
];
367+
368+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
369+
370+
}//end testPHP8UnionTypesSimple()
371+
372+
373+
/**
374+
* Verify recognition of PHP8 union type declaration with a bitwise or in the default value.
375+
*
376+
* @return void
377+
*/
378+
public function testPHP8UnionTypesSimpleWithBitwiseOrInDefault()
379+
{
380+
$expected = [];
381+
$expected[0] = [
382+
'name' => '$var',
383+
'content' => 'int|float $var = CONSTANT_A | CONSTANT_B',
384+
'default' => 'CONSTANT_A | CONSTANT_B',
385+
'pass_by_reference' => false,
386+
'variable_length' => false,
387+
'type_hint' => 'int|float',
388+
'nullable_type' => false,
389+
];
390+
391+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
392+
393+
}//end testPHP8UnionTypesSimpleWithBitwiseOrInDefault()
394+
395+
396+
/**
397+
* Verify recognition of PHP8 union type declaration with two classes.
398+
*
399+
* @return void
400+
*/
401+
public function testPHP8UnionTypesTwoClasses()
402+
{
403+
$expected = [];
404+
$expected[0] = [
405+
'name' => '$var',
406+
'content' => 'MyClassA|\Package\MyClassB $var',
407+
'pass_by_reference' => false,
408+
'variable_length' => false,
409+
'type_hint' => 'MyClassA|\Package\MyClassB',
410+
'nullable_type' => false,
411+
];
412+
413+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
414+
415+
}//end testPHP8UnionTypesTwoClasses()
416+
417+
418+
/**
419+
* Verify recognition of PHP8 union type declaration with all base types.
420+
*
421+
* @return void
422+
*/
423+
public function testPHP8UnionTypesAllBaseTypes()
424+
{
425+
$expected = [];
426+
$expected[0] = [
427+
'name' => '$var',
428+
'content' => 'array|bool|callable|int|float|null|object|string $var',
429+
'pass_by_reference' => false,
430+
'variable_length' => false,
431+
'type_hint' => 'array|bool|callable|int|float|null|object|string',
432+
'nullable_type' => false,
433+
];
434+
435+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
436+
437+
}//end testPHP8UnionTypesAllBaseTypes()
438+
439+
440+
/**
441+
* Verify recognition of PHP8 union type declaration with all pseudo types.
442+
*
443+
* @return void
444+
*/
445+
public function testPHP8UnionTypesAllPseudoTypes()
446+
{
447+
$expected = [];
448+
$expected[0] = [
449+
'name' => '$var',
450+
'content' => 'false|mixed|self|parent|iterable|Resource $var',
451+
'pass_by_reference' => false,
452+
'variable_length' => false,
453+
'type_hint' => 'false|mixed|self|parent|iterable|Resource',
454+
'nullable_type' => false,
455+
];
456+
457+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
458+
459+
}//end testPHP8UnionTypesAllPseudoTypes()
460+
461+
462+
/**
463+
* Verify recognition of PHP8 union type declaration with (illegal) nullability.
464+
*
465+
* @return void
466+
*/
467+
public function testPHP8UnionTypesNullable()
468+
{
469+
$expected = [];
470+
$expected[0] = [
471+
'name' => '$number',
472+
'content' => '?int|float $number',
473+
'pass_by_reference' => false,
474+
'variable_length' => false,
475+
'type_hint' => '?int|float',
476+
'nullable_type' => true,
477+
];
478+
479+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
480+
481+
}//end testPHP8UnionTypesNullable()
482+
483+
484+
/**
485+
* Verify recognition of PHP8 type declaration with (illegal) single type null.
486+
*
487+
* @return void
488+
*/
489+
public function testPHP8PseudoTypeNull()
490+
{
491+
$expected = [];
492+
$expected[0] = [
493+
'name' => '$var',
494+
'content' => 'null $var = null',
495+
'default' => 'null',
496+
'pass_by_reference' => false,
497+
'variable_length' => false,
498+
'type_hint' => 'null',
499+
'nullable_type' => false,
500+
];
501+
502+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
503+
504+
}//end testPHP8PseudoTypeNull()
505+
506+
507+
/**
508+
* Verify recognition of PHP8 type declaration with (illegal) single type false.
509+
*
510+
* @return void
511+
*/
512+
public function testPHP8PseudoTypeFalse()
513+
{
514+
$expected = [];
515+
$expected[0] = [
516+
'name' => '$var',
517+
'content' => 'false $var = false',
518+
'default' => 'false',
519+
'pass_by_reference' => false,
520+
'variable_length' => false,
521+
'type_hint' => 'false',
522+
'nullable_type' => false,
523+
];
524+
525+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
526+
527+
}//end testPHP8PseudoTypeFalse()
528+
529+
530+
/**
531+
* Verify recognition of PHP8 type declaration with (illegal) type false combined with type bool.
532+
*
533+
* @return void
534+
*/
535+
public function testPHP8PseudoTypeFalseAndBool()
536+
{
537+
$expected = [];
538+
$expected[0] = [
539+
'name' => '$var',
540+
'content' => 'bool|false $var = false',
541+
'default' => 'false',
542+
'pass_by_reference' => false,
543+
'variable_length' => false,
544+
'type_hint' => 'bool|false',
545+
'nullable_type' => false,
546+
];
547+
548+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
549+
550+
}//end testPHP8PseudoTypeFalseAndBool()
551+
552+
553+
/**
554+
* Verify recognition of PHP8 type declaration with (illegal) type object combined with a class name.
555+
*
556+
* @return void
557+
*/
558+
public function testPHP8ObjectAndClass()
559+
{
560+
$expected = [];
561+
$expected[0] = [
562+
'name' => '$var',
563+
'content' => 'object|ClassName $var',
564+
'pass_by_reference' => false,
565+
'variable_length' => false,
566+
'type_hint' => 'object|ClassName',
567+
'nullable_type' => false,
568+
];
569+
570+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
571+
572+
}//end testPHP8ObjectAndClass()
573+
574+
575+
/**
576+
* Verify recognition of PHP8 type declaration with (illegal) type iterable combined with array/Traversable.
577+
*
578+
* @return void
579+
*/
580+
public function testPHP8PseudoTypeIterableAndArray()
581+
{
582+
$expected = [];
583+
$expected[0] = [
584+
'name' => '$var',
585+
'content' => 'iterable|array|Traversable $var',
586+
'pass_by_reference' => false,
587+
'variable_length' => false,
588+
'type_hint' => 'iterable|array|Traversable',
589+
'nullable_type' => false,
590+
];
591+
592+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
593+
594+
}//end testPHP8PseudoTypeIterableAndArray()
595+
596+
597+
/**
598+
* Verify recognition of PHP8 type declaration with (illegal) duplicate types.
599+
*
600+
* @return void
601+
*/
602+
public function testPHP8DuplicateTypeInUnionWhitespaceAndComment()
603+
{
604+
$expected = [];
605+
$expected[0] = [
606+
'name' => '$var',
607+
'content' => 'int | string /*comment*/ | INT $var',
608+
'pass_by_reference' => false,
609+
'variable_length' => false,
610+
'type_hint' => 'int|string|INT',
611+
'nullable_type' => false,
612+
];
613+
614+
$this->getMethodParametersTestHelper('/* '.__FUNCTION__.' */', $expected);
615+
616+
}//end testPHP8DuplicateTypeInUnionWhitespaceAndComment()
617+
618+
343619
/**
344620
* Test helper.
345621
*
@@ -350,7 +626,7 @@ public function testNamespaceOperatorTypeHint()
350626
*/
351627
private function getMethodParametersTestHelper($commentString, $expected)
352628
{
353-
$function = $this->getTargetToken($commentString, [T_FUNCTION, T_FN]);
629+
$function = $this->getTargetToken($commentString, [T_FUNCTION, T_CLOSURE, T_FN]);
354630
$found = self::$phpcsFile->getMethodParameters($function);
355631

356632
$this->assertArraySubset($expected, $found, true);

0 commit comments

Comments
 (0)