Skip to content

Commit ae89621

Browse files
committed
feat: add dynamic parameter type extensions
1 parent 0fbb507 commit ae89621

15 files changed

+225
-2
lines changed

src/Analyser/NodeScopeResolver.php

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
use PHPStan\DependencyInjection\AutowiredParameter;
6767
use PHPStan\DependencyInjection\AutowiredService;
6868
use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider;
69+
use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider;
6970
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
7071
use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider;
7172
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
@@ -274,6 +275,7 @@ public function __construct(
274275
private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
275276
private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider,
276277
private readonly ParameterClosureThisExtensionProvider $parameterClosureThisExtensionProvider,
278+
private readonly DynamicParameterTypeExtensionProvider $dynamicParameterTypeExtensionProvider,
277279
private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider,
278280
private readonly ScopeFactory $scopeFactory,
279281
#[AutowiredParameter]
@@ -5224,7 +5226,8 @@ private function processArgs(
52245226
}
52255227

52265228
if ($parameter !== null) {
5227-
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
5229+
$overwritingParameterType = $this->getParameterTypeFromDynamicParameterTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass)
5230+
?? $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
52285231

52295232
if ($overwritingParameterType !== null) {
52305233
$parameterType = $overwritingParameterType;
@@ -5279,7 +5282,8 @@ private function processArgs(
52795282
}
52805283

52815284
if ($parameter !== null) {
5282-
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
5285+
$overwritingParameterType = $this->getParameterTypeFromDynamicParameterTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass)
5286+
?? $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
52835287

52845288
if ($overwritingParameterType !== null) {
52855289
$parameterType = $overwritingParameterType;
@@ -5428,6 +5432,46 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
54285432
return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints);
54295433
}
54305434

5435+
/**
5436+
* @param MethodReflection|FunctionReflection|null $calleeReflection
5437+
*/
5438+
private function getParameterTypeFromDynamicParameterTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type
5439+
{
5440+
if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) {
5441+
foreach ($this->dynamicParameterTypeExtensionProvider->getDynamicFunctionParameterTypeExtensions() as $dynamicFunctionParameterTypeExtension) {
5442+
if (!$dynamicFunctionParameterTypeExtension->isFunctionSupported($calleeReflection, $parameter)) {
5443+
continue;
5444+
}
5445+
$type = $dynamicFunctionParameterTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope);
5446+
if ($type !== null) {
5447+
return $type;
5448+
}
5449+
}
5450+
} elseif ($callLike instanceof StaticCall && $calleeReflection instanceof MethodReflection) {
5451+
foreach ($this->dynamicParameterTypeExtensionProvider->getDynamicStaticMethodParameterTypeExtensions() as $dynamicStaticMethodParameterTypeExtension) {
5452+
if (!$dynamicStaticMethodParameterTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) {
5453+
continue;
5454+
}
5455+
$type = $dynamicStaticMethodParameterTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope);
5456+
if ($type !== null) {
5457+
return $type;
5458+
}
5459+
}
5460+
} elseif ($callLike instanceof MethodCall && $calleeReflection instanceof MethodReflection) {
5461+
foreach ($this->dynamicParameterTypeExtensionProvider->getDynamicMethodParameterTypeExtensions() as $dynamicMethodParameterTypeExtension) {
5462+
if (!$dynamicMethodParameterTypeExtension->isMethodSupported($calleeReflection, $parameter)) {
5463+
continue;
5464+
}
5465+
$type = $dynamicMethodParameterTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope);
5466+
if ($type !== null) {
5467+
return $type;
5468+
}
5469+
}
5470+
}
5471+
5472+
return null;
5473+
}
5474+
54315475
/**
54325476
* @param MethodReflection|FunctionReflection|null $calleeReflection
54335477
*/
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DependencyInjection\Type;
4+
5+
use PHPStan\Type\DynamicFunctionParameterTypeExtension;
6+
use PHPStan\Type\DynamicMethodParameterTypeExtension;
7+
use PHPStan\Type\DynamicStaticMethodParameterTypeExtension;
8+
9+
interface DynamicParameterTypeExtensionProvider
10+
{
11+
12+
/** @return DynamicFunctionParameterTypeExtension[] */
13+
public function getDynamicFunctionParameterTypeExtensions(): array;
14+
15+
/** @return DynamicMethodParameterTypeExtension[] */
16+
public function getDynamicMethodParameterTypeExtensions(): array;
17+
18+
/** @return DynamicStaticMethodParameterTypeExtension[] */
19+
public function getDynamicStaticMethodParameterTypeExtensions(): array;
20+
21+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\DependencyInjection\Type;
4+
5+
use PHPStan\DependencyInjection\AutowiredService;
6+
use PHPStan\DependencyInjection\Container;
7+
8+
#[AutowiredService(as: DynamicParameterTypeExtensionProvider::class)]
9+
final class LazyDynamicParameterTypeExtensionProvider implements DynamicParameterTypeExtensionProvider
10+
{
11+
12+
public const FUNCTION_TAG = 'phpstan.dynamicFunctionParameterTypeExtension';
13+
public const METHOD_TAG = 'phpstan.dynamicMethodParameterTypeExtension';
14+
public const STATIC_METHOD_TAG = 'phpstan.dynamicStaticMethodParameterTypeExtension';
15+
16+
public function __construct(private Container $container)
17+
{
18+
}
19+
20+
public function getDynamicFunctionParameterTypeExtensions(): array
21+
{
22+
return $this->container->getServicesByTag(self::FUNCTION_TAG);
23+
}
24+
25+
public function getDynamicMethodParameterTypeExtensions(): array
26+
{
27+
return $this->container->getServicesByTag(self::METHOD_TAG);
28+
}
29+
30+
public function getDynamicStaticMethodParameterTypeExtensions(): array
31+
{
32+
return $this->container->getServicesByTag(self::STATIC_METHOD_TAG);
33+
}
34+
35+
}

src/DependencyInjection/Type/LazyParameterClosureTypeExtensionProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
use PHPStan\DependencyInjection\AutowiredService;
66
use PHPStan\DependencyInjection\Container;
77

8+
/**
9+
* @deprecated
10+
* @see \PHPStan\DependencyInjection\Type\LazyDynamicParameterTypeExtensionProvider
11+
*/
812
#[AutowiredService(as: ParameterClosureTypeExtensionProvider::class)]
913
final class LazyParameterClosureTypeExtensionProvider implements ParameterClosureTypeExtensionProvider
1014
{

src/DependencyInjection/Type/ParameterClosureTypeExtensionProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
use PHPStan\Type\MethodParameterClosureTypeExtension;
77
use PHPStan\Type\StaticMethodParameterClosureTypeExtension;
88

9+
/**
10+
* @deprecated
11+
* @see \PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider
12+
*/
913
interface ParameterClosureTypeExtensionProvider
1014
{
1115

src/DependencyInjection/ValidateServiceTagsExtension.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use PHPStan\Broker\BrokerFactory;
1111
use PHPStan\Collectors\Collector;
1212
use PHPStan\Collectors\RegistryFactory as CollectorRegistryFactory;
13+
use PHPStan\DependencyInjection\Type\LazyDynamicParameterTypeExtensionProvider;
1314
use PHPStan\DependencyInjection\Type\LazyDynamicThrowTypeExtensionProvider;
1415
use PHPStan\DependencyInjection\Type\LazyParameterClosureThisExtensionProvider;
1516
use PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider;
@@ -41,10 +42,13 @@
4142
use PHPStan\Rules\RestrictedUsage\RestrictedPropertyUsageExtension;
4243
use PHPStan\Rules\Rule;
4344
use PHPStan\ShouldNotHappenException;
45+
use PHPStan\Type\DynamicFunctionParameterTypeExtension;
4446
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
4547
use PHPStan\Type\DynamicFunctionThrowTypeExtension;
48+
use PHPStan\Type\DynamicMethodParameterTypeExtension;
4649
use PHPStan\Type\DynamicMethodReturnTypeExtension;
4750
use PHPStan\Type\DynamicMethodThrowTypeExtension;
51+
use PHPStan\Type\DynamicStaticMethodParameterTypeExtension;
4852
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
4953
use PHPStan\Type\DynamicStaticMethodThrowTypeExtension;
5054
use PHPStan\Type\ExpressionTypeResolverExtension;
@@ -93,6 +97,9 @@ final class ValidateServiceTagsExtension extends CompilerExtension
9397
FunctionParameterClosureThisExtension::class => LazyParameterClosureThisExtensionProvider::FUNCTION_TAG,
9498
MethodParameterClosureThisExtension::class => LazyParameterClosureThisExtensionProvider::METHOD_TAG,
9599
StaticMethodParameterClosureThisExtension::class => LazyParameterClosureThisExtensionProvider::STATIC_METHOD_TAG,
100+
DynamicFunctionParameterTypeExtension::class => LazyDynamicParameterTypeExtensionProvider::FUNCTION_TAG,
101+
DynamicMethodParameterTypeExtension::class => LazyDynamicParameterTypeExtensionProvider::METHOD_TAG,
102+
DynamicStaticMethodParameterTypeExtension::class => LazyDynamicParameterTypeExtensionProvider::STATIC_METHOD_TAG,
96103
FunctionParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::FUNCTION_TAG,
97104
MethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::METHOD_TAG,
98105
StaticMethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::STATIC_METHOD_TAG,

src/Testing/RuleTestCase.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\Collectors\Collector;
1717
use PHPStan\Collectors\Registry as CollectorRegistry;
1818
use PHPStan\Dependency\DependencyResolver;
19+
use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider;
1920
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
2021
use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider;
2122
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
@@ -107,6 +108,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser
107108
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
108109
$readWritePropertiesExtensions !== [] ? new DirectReadWritePropertiesExtensionProvider($readWritePropertiesExtensions) : self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
109110
self::getContainer()->getByType(ParameterClosureThisExtensionProvider::class),
111+
self::getContainer()->getByType(DynamicParameterTypeExtensionProvider::class),
110112
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
111113
self::createScopeFactory($reflectionProvider, $typeSpecifier),
112114
$this->shouldPolluteScopeWithLoopInitialAssignments(),

src/Testing/TypeInferenceTestCase.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use PHPStan\Analyser\NodeScopeResolver;
99
use PHPStan\Analyser\Scope;
1010
use PHPStan\Analyser\ScopeContext;
11+
use PHPStan\DependencyInjection\Type\DynamicParameterTypeExtensionProvider;
1112
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
1213
use PHPStan\DependencyInjection\Type\ParameterClosureThisExtensionProvider;
1314
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
@@ -85,6 +86,7 @@ public static function processFile(
8586
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
8687
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
8788
self::getContainer()->getByType(ParameterClosureThisExtensionProvider::class),
89+
self::getContainer()->getByType(DynamicParameterTypeExtensionProvider::class),
8890
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
8991
self::createScopeFactory($reflectionProvider, $typeSpecifier),
9092
self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'),
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\FunctionReflection;
8+
use PHPStan\Reflection\ParameterReflection;
9+
10+
/**
11+
* This is the interface for dynamic parameter type extensions for functions.
12+
*
13+
* To register it in the configuration file use the `phpstan.dynamicFunctionParameterTypeExtension` service tag:
14+
*
15+
* ```
16+
* services:
17+
* -
18+
* class: App\PHPStan\MyExtension
19+
* tags:
20+
* - phpstan.dynamicFunctionParameterTypeExtension
21+
* ```
22+
*
23+
* @api
24+
*/
25+
interface DynamicFunctionParameterTypeExtension
26+
{
27+
28+
public function isFunctionSupported(FunctionReflection $functionReflection, ParameterReflection $parameter): bool;
29+
30+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, ParameterReflection $parameter, Scope $scope): ?Type;
31+
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\Reflection\MethodReflection;
8+
use PHPStan\Reflection\ParameterReflection;
9+
10+
/**
11+
* This is the interface for dynamic parameter type extensions for methods.
12+
*
13+
* To register it in the configuration file use the `phpstan.dynamicMethodParameterTypeExtension` service tag:
14+
*
15+
* ```
16+
* services:
17+
* -
18+
* class: App\PHPStan\MyExtension
19+
* tags:
20+
* - phpstan.dynamicMethodParameterTypeExtension
21+
* ```
22+
*
23+
* @api
24+
*/
25+
interface DynamicMethodParameterTypeExtension
26+
{
27+
28+
public function isMethodSupported(MethodReflection $methodReflection, ParameterReflection $parameter): bool;
29+
30+
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, ParameterReflection $parameter, Scope $scope): ?Type;
31+
32+
}

0 commit comments

Comments
 (0)