Skip to content

Commit

Permalink
Support for native union types
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Jul 8, 2020
1 parent b9c29a5 commit 39f8909
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 1 deletion.
1 change: 1 addition & 0 deletions build/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ parameters:
- %rootDir%/tests/PHPStan/Analyser/traits/*
- %rootDir%/tests/notAutoloaded/*
- %rootDir%/tests/PHPStan/Generics/functions.php
- %rootDir%/tests/PHPStan/Reflection/UnionTypesTest.php
ignoreErrors:
- '#^Dynamic call to static method PHPUnit\\Framework\\\S+\(\)\.$#'
- '#should be contravariant with parameter \$node \(PhpParser\\Node\) of method PHPStan\\Rules\\Rule<PhpParser\\Node>::processNode\(\)$#'
Expand Down
3 changes: 2 additions & 1 deletion conf/config.neon
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
parameters:
bootstrap: null
bootstrapFiles: []
bootstrapFiles:
- ../stubs/runtime/ReflectionUnionType.php
excludes_analyse: []
autoload_directories: []
autoload_files: []
Expand Down
10 changes: 10 additions & 0 deletions src/Type/TypehintHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use PHPStan\Broker\Broker;
use PHPStan\Type\Generic\TemplateTypeHelper;
use ReflectionNamedType;
use ReflectionType;
use ReflectionUnionType;

class TypehintHelper
{
Expand Down Expand Up @@ -62,6 +64,14 @@ public static function decideTypeFromReflection(
return $phpDocType ?? new MixedType();
}

if ($reflectionType instanceof ReflectionUnionType) {
$type = TypeCombinator::union(...array_map(static function (ReflectionType $type) use ($selfClass): Type {
return self::decideTypeFromReflection($type, null, $selfClass, false);
}, $reflectionType->getTypes()));

return self::decideType($type, $phpDocType);
}

if (!$reflectionType instanceof ReflectionNamedType) {
throw new \PHPStan\ShouldNotHappenException(sprintf('Unexpected type: %s', get_class($reflectionType)));
}
Expand Down
16 changes: 16 additions & 0 deletions stubs/runtime/ReflectionUnionType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

if (class_exists('ReflectionUnionType', false)) {
return;
}

class ReflectionUnionType extends ReflectionType
{

/** @return ReflectionType[] */
public function getTypes()
{
return [];
}

}
12 changes: 12 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9989,6 +9989,17 @@ public function dataGraphicsDrawReturnTypes(): array
return $this->gatherAssertTypes(__DIR__ . '/data/graphics-draw-return-types.php');
}

public function dataNativeUnionTypes(): array
{
if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) {
return [];
}

require_once __DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php';

return $this->gatherAssertTypes(__DIR__ . '/../Reflection/data/unionTypes.php');
}

/**
* @dataProvider dataBug2574
* @dataProvider dataBug2577
Expand Down Expand Up @@ -10049,6 +10060,7 @@ public function dataGraphicsDrawReturnTypes(): array
* @dataProvider dataOverrideVariableCertaintyInRootScope
* @dataProvider dataBitwiseNot
* @dataProvider dataGraphicsDrawReturnTypes
* @dataProvider dataNativeUnionTypes
* @param string $assertType
* @param string $file
* @param mixed ...$args
Expand Down
49 changes: 49 additions & 0 deletions tests/PHPStan/Reflection/UnionTypesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

use NativeUnionTypes\Foo;
use PhpParser\Node\Name;
use PHPStan\Testing\TestCase;
use PHPStan\Type\UnionType;
use PHPStan\Type\VerbosityLevel;

class UnionTypesTest extends TestCase
{

public function testUnionTypes(): void
{
if (PHP_VERSION_ID < 80000 && !self::$useStaticReflectionProvider) {
$this->markTestSkipped('Test requires PHP 8.0');
}

require_once __DIR__ . '/../../../stubs/runtime/ReflectionUnionType.php';

$reflectionProvider = $this->createBroker();
$class = $reflectionProvider->getClass(Foo::class);
$propertyType = $class->getNativeProperty('fooProp')->getNativeType();
$this->assertInstanceOf(UnionType::class, $propertyType);
$this->assertSame('bool|int', $propertyType->describe(VerbosityLevel::precise()));

$method = $class->getNativeMethod('doFoo');
$methodVariant = ParametersAcceptorSelector::selectSingle($method->getVariants());
$methodReturnType = $methodVariant->getReturnType();
$this->assertInstanceOf(UnionType::class, $methodReturnType);
$this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $methodReturnType->describe(VerbosityLevel::precise()));

$methodParameterType = $methodVariant->getParameters()[0]->getType();
$this->assertInstanceOf(UnionType::class, $methodParameterType);
$this->assertSame('bool|int', $methodParameterType->describe(VerbosityLevel::precise()));

$function = $reflectionProvider->getFunction(new Name('NativeUnionTypes\doFoo'), null);
$functionVariant = ParametersAcceptorSelector::selectSingle($function->getVariants());
$functionReturnType = $functionVariant->getReturnType();
$this->assertInstanceOf(UnionType::class, $functionReturnType);
$this->assertSame('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $functionReturnType->describe(VerbosityLevel::precise()));

$functionParameterType = $functionVariant->getParameters()[0]->getType();
$this->assertInstanceOf(UnionType::class, $functionParameterType);
$this->assertSame('bool|int', $functionParameterType->describe(VerbosityLevel::precise()));
}

}
42 changes: 42 additions & 0 deletions tests/PHPStan/Reflection/data/unionTypes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php // lint >= 8.0

namespace NativeUnionTypes;

use function PHPStan\Analyser\assertType;

class Foo
{

public int|bool $fooProp;

public function doFoo(int|bool $foo): self|Bar
{
assertType('bool|int', $foo);
assertType('bool|int', $this->fooProp);
}

}

class Bar
{

}

function doFoo(int|bool $foo): Foo|Bar
{
assertType('bool|int', $foo);
}

function (Foo $foo): void {
assertType('bool|int', $foo->fooProp);
assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $foo->doFoo(1));
assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', doFoo(1));
};

function (): void {
$f = function (int|bool $foo): Foo|Bar {
assertType('bool|int', $foo);
};

assertType('NativeUnionTypes\\Bar|NativeUnionTypes\\Foo', $f(1));
};

0 comments on commit 39f8909

Please sign in to comment.