Skip to content

Commit

Permalink
add new ClosureTypeChangingExtension
Browse files Browse the repository at this point in the history
  • Loading branch information
canvural committed May 23, 2024
1 parent 49f87ce commit 8ee632f
Show file tree
Hide file tree
Showing 13 changed files with 418 additions and 0 deletions.
4 changes: 4 additions & 0 deletions conf/config.neon
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,10 @@ services:
class: PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider
factory: PHPStan\DependencyInjection\Type\LazyDynamicThrowTypeExtensionProvider

-
class: PHPStan\DependencyInjection\Type\ClosureTypeChangingExtensionProvider
factory: PHPStan\DependencyInjection\Type\LazyClosureTypeChangingExtensionProvider

-
class: PHPStan\File\FileHelper
arguments:
Expand Down
45 changes: 45 additions & 0 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
use PHPStan\BetterReflection\SourceLocator\Ast\Strategy\NodeToReflection;
use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider;
use PHPStan\DependencyInjection\Type\ClosureTypeChangingExtensionProvider;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\File\FileHelper;
use PHPStan\File\FileReader;
Expand Down Expand Up @@ -244,6 +245,7 @@ public function __construct(
private readonly TypeSpecifier $typeSpecifier,
private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider,
private readonly ClosureTypeChangingExtensionProvider $closureTypeChangingExtensionProvider,
private readonly ScopeFactory $scopeFactory,
private readonly bool $polluteScopeWithLoopInitialAssignments,
private readonly bool $polluteScopeWithAlwaysIterableForeach,
Expand Down Expand Up @@ -3928,6 +3930,49 @@ private function processClosureNode(
?Type $passedToType,
): ProcessClosureResult
{
if ($stmt instanceof Node\Stmt\Expression && $stmt->expr instanceof Expr\CallLike) {
if ($stmt->expr instanceof FuncCall && $stmt->expr->name instanceof Name && $this->reflectionProvider->hasFunction($stmt->expr->name, $scope)) {
$functionReflection = $this->reflectionProvider->getFunction($stmt->expr->name, $scope);

foreach ($this->closureTypeChangingExtensionProvider->getFunctionClosureTypeChangingExtensions() as $functionClosureTypeChangingExtension) {
if ($functionClosureTypeChangingExtension->isFunctionSupported($functionReflection)) {
$passedToType = $functionClosureTypeChangingExtension->getTypeFromFunctionCall($functionReflection, $stmt->expr, $scope);
break;
}
}
} elseif ($stmt->expr instanceof MethodCall && $stmt->expr->name instanceof Node\Identifier) {
$methodReflection = $scope->getMethodReflection($scope->getType($stmt->expr->var), $stmt->expr->name->toString());

if ($methodReflection !== null) {
foreach ($this->closureTypeChangingExtensionProvider->getMethodClosureTypeChangingExtensions() as $methodClosureTypeChangingExtension) {
if ($methodClosureTypeChangingExtension->isMethodSupported($methodReflection)) {
$passedToType = $methodClosureTypeChangingExtension->getTypeFromMethodCall($methodReflection, $stmt->expr, $scope);
break;
}
}
}
} elseif ($stmt->expr instanceof StaticCall && $stmt->expr->name instanceof Node\Identifier) {
$methodName = $stmt->expr->name->toString();

if ($stmt->expr->class instanceof Name) {
$calledOnType = $scope->resolveTypeByName($stmt->expr->class);
} else {
$calledOnType = $scope->getType($stmt->expr->class)->getObjectTypeOrClassStringObjectType();
}

$staticMethodReflection = $scope->getMethodReflection($calledOnType, $methodName);

if ($staticMethodReflection !== null) {
foreach ($this->closureTypeChangingExtensionProvider->getStaticMethodClosureTypeChangingExtensions() as $staticMethodClosureTypeChangingExtension) {
if ($staticMethodClosureTypeChangingExtension->isStaticMethodSupported($staticMethodReflection)) {
$passedToType = $staticMethodClosureTypeChangingExtension->getTypeFromStaticMethodCall($staticMethodReflection, $stmt->expr, $scope);
break;
}
}
}
}
}

foreach ($expr->params as $param) {
$this->processParamNode($stmt, $param, $scope, $nodeCallback);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php declare(strict_types = 1);

namespace PHPStan\DependencyInjection\Type;

use PHPStan\Type\FunctionClosureTypeChangingExtension;
use PHPStan\Type\MethodClosureTypeChangingExtension;
use PHPStan\Type\StaticMethodClosureTypeChangingExtension;

interface ClosureTypeChangingExtensionProvider
{

/** @return FunctionClosureTypeChangingExtension[] */
public function getFunctionClosureTypeChangingExtensions(): array;

/** @return MethodClosureTypeChangingExtension[] */
public function getMethodClosureTypeChangingExtensions(): array;

/** @return StaticMethodClosureTypeChangingExtension[] */
public function getStaticMethodClosureTypeChangingExtensions(): array;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types = 1);

namespace PHPStan\DependencyInjection\Type;

use PHPStan\DependencyInjection\Container;

class LazyClosureTypeChangingExtensionProvider implements ClosureTypeChangingExtensionProvider
{

public const FUNCTION_TAG = 'phpstan.functionClosureTypeChangingExtension';
public const METHOD_TAG = 'phpstan.methodClosureTypeChangingExtension';
public const STATIC_METHOD_TAG = 'phpstan.staticMethodClosureTypeChangingExtension';

public function __construct(private Container $container)
{
}

public function getFunctionClosureTypeChangingExtensions(): array
{
return $this->container->getServicesByTag(self::FUNCTION_TAG);
}

public function getMethodClosureTypeChangingExtensions(): array
{
return $this->container->getServicesByTag(self::METHOD_TAG);
}

public function getStaticMethodClosureTypeChangingExtensions(): array
{
return $this->container->getServicesByTag(self::STATIC_METHOD_TAG);
}

}
2 changes: 2 additions & 0 deletions src/Testing/RuleTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPStan\Collectors\Collector;
use PHPStan\Collectors\Registry as CollectorRegistry;
use PHPStan\Dependency\DependencyResolver;
use PHPStan\DependencyInjection\Type\ClosureTypeChangingExtensionProvider;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\File\FileHelper;
use PHPStan\Php\PhpVersion;
Expand Down Expand Up @@ -91,6 +92,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
$readWritePropertiesExtensions !== [] ? new DirectReadWritePropertiesExtensionProvider($readWritePropertiesExtensions) : self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
self::getContainer()->getByType(ClosureTypeChangingExtensionProvider::class),
self::createScopeFactory($reflectionProvider, $typeSpecifier),
$this->shouldPolluteScopeWithLoopInitialAssignments(),
$this->shouldPolluteScopeWithAlwaysIterableForeach(),
Expand Down
2 changes: 2 additions & 0 deletions src/Testing/TypeInferenceTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\ScopeContext;
use PHPStan\DependencyInjection\Type\ClosureTypeChangingExtensionProvider;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\File\FileHelper;
use PHPStan\Php\PhpVersion;
Expand Down Expand Up @@ -62,6 +63,7 @@ public static function processFile(
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
self::getContainer()->getByType(ClosureTypeChangingExtensionProvider::class),
self::createScopeFactory($reflectionProvider, $typeSpecifier),
self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'),
self::getContainer()->getParameter('polluteScopeWithAlwaysIterableForeach'),
Expand Down
16 changes: 16 additions & 0 deletions src/Type/FunctionClosureTypeChangingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use PhpParser\Node\Expr\FuncCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\FunctionReflection;

interface FunctionClosureTypeChangingExtension
{

public function isFunctionSupported(FunctionReflection $functionReflection): bool;

public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type;

}
16 changes: 16 additions & 0 deletions src/Type/MethodClosureTypeChangingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;

interface MethodClosureTypeChangingExtension
{

public function isMethodSupported(MethodReflection $methodReflection): bool;

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type;

}
16 changes: 16 additions & 0 deletions src/Type/StaticMethodClosureTypeChangingExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type;

use PhpParser\Node\Expr\StaticCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;

interface StaticMethodClosureTypeChangingExtension
{

public function isStaticMethodSupported(MethodReflection $methodReflection): bool;

public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type;

}
2 changes: 2 additions & 0 deletions tests/PHPStan/Analyser/AnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use PHPStan\Collectors\Registry as CollectorRegistry;
use PHPStan\Dependency\DependencyResolver;
use PHPStan\Dependency\ExportedNodeResolver;
use PHPStan\DependencyInjection\Type\ClosureTypeChangingExtensionProvider;
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
use PHPStan\Node\Printer\ExprPrinter;
use PHPStan\Node\Printer\Printer;
Expand Down Expand Up @@ -702,6 +703,7 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser
$typeSpecifier,
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
self::getContainer()->getByType(ClosureTypeChangingExtensionProvider::class),
self::createScopeFactory($reflectionProvider, $typeSpecifier),
false,
true,
Expand Down
35 changes: 35 additions & 0 deletions tests/PHPStan/Analyser/ClosureTypeChangingExtensionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Testing\TypeInferenceTestCase;

class ClosureTypeChangingExtensionTest extends TypeInferenceTestCase
{

public function dataFileAsserts(): iterable
{
yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-type-changing-extension.php');
}

/**
* @dataProvider dataFileAsserts
* @param mixed ...$args
*/
public function testFileAsserts(
string $assertType,
string $file,
...$args,
): void
{
$this->assertFileAsserts($assertType, $file, ...$args);
}

public static function getAdditionalConfigFiles(): array
{
return [
__DIR__ . '/closure-type-changing-extension.neon',
];
}

}
16 changes: 16 additions & 0 deletions tests/PHPStan/Analyser/closure-type-changing-extension.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
-
class: ClosureTypeChangingExtension\FunctionClosureTypeChangingExtension
tags:
- phpstan.functionClosureTypeChangingExtension

-
class: ClosureTypeChangingExtension\MethodClosureTypeChangingExtension
tags:
- phpstan.methodClosureTypeChangingExtension

-
class: ClosureTypeChangingExtension\StaticMethodClosureTypeChangingExtension
tags:
- phpstan.staticMethodClosureTypeChangingExtension

Loading

0 comments on commit 8ee632f

Please sign in to comment.