Skip to content

Commit

Permalink
Support for native pure intersection types
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Nov 4, 2021
1 parent 59be92f commit be05557
Show file tree
Hide file tree
Showing 8 changed files with 70 additions and 9 deletions.
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -23,7 +23,7 @@
"nette/utils": "^3.1.3",
"nikic/php-parser": "4.13.1",
"ondram/ci-detector": "^3.4.0",
"ondrejmirtes/better-reflection": "4.3.74",
"ondrejmirtes/better-reflection": "4.3.75",
"phpstan/php-8-stubs": "^0.1.23",
"phpstan/phpdoc-parser": "^1.2.0",
"react/child-process": "^0.6.4",
Expand Down
14 changes: 7 additions & 7 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions conf/config.neon
Expand Up @@ -5,6 +5,7 @@ parameters:
- ../stubs/runtime/ReflectionUnionType.php
- ../stubs/runtime/ReflectionAttribute.php
- ../stubs/runtime/Attribute.php
- ../stubs/runtime/ReflectionIntersectionType.php
excludes_analyse: []
excludePaths: null
level: null
Expand Down
Expand Up @@ -79,7 +79,7 @@ private function isClassBlacklisted(string $className): bool
if (!class_exists($className, false)) {
return true;
}
if (in_array(strtolower($className), ['reflectionuniontype', 'attribute', 'returntypewillchange'], true)) {
if (in_array(strtolower($className), ['reflectionuniontype', 'attribute', 'returntypewillchange', 'reflectionintersectiontype'], true)) {
return true;
}
$reflection = new \ReflectionClass($className);
Expand Down
9 changes: 9 additions & 0 deletions src/Type/TypehintHelper.php
Expand Up @@ -5,6 +5,7 @@
use PHPStan\Reflection\ReflectionProviderStaticAccessor;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\Generic\TemplateTypeHelper;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionType;
use ReflectionUnionType;
Expand Down Expand Up @@ -87,6 +88,14 @@ public static function decideTypeFromReflection(
return self::decideType($type, $phpDocType);
}

if ($reflectionType instanceof ReflectionIntersectionType) {
$type = TypeCombinator::intersect(...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
18 changes: 18 additions & 0 deletions stubs/runtime/ReflectionIntersectionType.php
@@ -0,0 +1,18 @@
<?php

if (\PHP_VERSION_ID < 80100) {
if (class_exists('ReflectionIntersectionType', false)) {
return;
}

class ReflectionIntersectionType extends ReflectionType
{

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

}
}
4 changes: 4 additions & 0 deletions tests/PHPStan/Analyser/NodeScopeResolverTest.php
Expand Up @@ -536,6 +536,10 @@ public function dataFileAsserts(): iterable
yield from $this->gatherAssertTypes(__DIR__ . '/data/never.php');
}

if (PHP_VERSION_ID >= 80100 || self::$useStaticReflectionProvider) {
yield from $this->gatherAssertTypes(__DIR__ . '/data/native-intersection.php');
}

yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-2760.php');
}

Expand Down
29 changes: 29 additions & 0 deletions tests/PHPStan/Analyser/data/native-intersection.php
@@ -0,0 +1,29 @@
<?php // lint >= 8.1

namespace NativeIntersection;

use function PHPStan\Testing\assertType;

interface A
{

}

interface B
{

}

class Foo
{

private A&B $prop;

public function doFoo(A&B $ab): A&B
{
assertType('NativeIntersection\A&NativeIntersection\B', $this->prop);
assertType('NativeIntersection\A&NativeIntersection\B', $ab);
assertType('NativeIntersection\A&NativeIntersection\B', $this->doFoo($ab));
}

}

0 comments on commit be05557

Please sign in to comment.