Skip to content

Commit

Permalink
Fix CacheInterface::get() return type
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored Dec 22, 2023
1 parent 27ff633 commit 34b3c43
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 0 deletions.
5 changes: 5 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -336,3 +336,8 @@ services:
tags:
- phpstan.properties.readWriteExtension
- phpstan.additionalConstructorsExtension

# CacheInterface::get() return type
-
factory: PHPStan\Type\Symfony\CacheInterfaceGetDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
48 changes: 48 additions & 0 deletions src/Type/Symfony/CacheInterfaceGetDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php declare(strict_types = 1);

namespace PHPStan\Type\Symfony;

use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\GeneralizePrecision;
use PHPStan\Type\Type;

final class CacheInterfaceGetDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{

public function getClass(): string
{
return 'Symfony\Contracts\Cache\CacheInterface';
}

public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'get';
}

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type
{
if (!isset($methodCall->getArgs()[1])) {
return null;
}

$callbackReturnType = $scope->getType($methodCall->getArgs()[1]->value);
if ($callbackReturnType->isCallable()->yes()) {
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$methodCall->getArgs(),
$callbackReturnType->getCallableParametersAcceptors($scope)
);
$returnType = $parametersAcceptor->getReturnType();

// generalize template parameters
return $returnType->generalize(GeneralizePrecision::templateArgument());
}

return null;
}

}
20 changes: 20 additions & 0 deletions tests/Type/Symfony/data/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ function testCacheCallable(\Symfony\Contracts\Cache\CacheInterface $cache): voi
assertType('string', $result);
};

/**
* @param callable():string $fn
*/
function testNonScalarCacheCallable(\Symfony\Contracts\Cache\CacheInterface $cache, callable $fn): void {
$result = $cache->get('foo', $fn);

assertType('string', $result);
};


/**
* @param callable():non-empty-string $fn
*/
function testCacheCallableReturnTypeGeneralization(\Symfony\Contracts\Cache\CacheInterface $cache, callable $fn): void {
$result = $cache->get('foo', $fn);

assertType('string', $result);
};


/**
* @param \Symfony\Contracts\Cache\CallbackInterface<\stdClass> $cb
*/
Expand Down

0 comments on commit 34b3c43

Please sign in to comment.