Skip to content

Commit eae7ce9

Browse files
committed
feat: add dynamic parameter type extensions
1 parent 774f1ab commit eae7ce9

16 files changed

+351
-2
lines changed

phpstan-baseline.neon

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,60 @@ parameters:
4242
count: 1
4343
path: src/Analyser/MutatingScope.php
4444

45+
-
46+
rawMessage: 'Call to method getFunctionParameterClosureTypeExtensions() of deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider.'
47+
identifier: method.deprecatedInterface
48+
count: 1
49+
path: src/Analyser/NodeScopeResolver.php
50+
51+
-
52+
rawMessage: 'Call to method getMethodParameterClosureTypeExtensions() of deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider.'
53+
identifier: method.deprecatedInterface
54+
count: 1
55+
path: src/Analyser/NodeScopeResolver.php
56+
57+
-
58+
rawMessage: 'Call to method getStaticMethodParameterClosureTypeExtensions() of deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider.'
59+
identifier: method.deprecatedInterface
60+
count: 1
61+
path: src/Analyser/NodeScopeResolver.php
62+
63+
-
64+
rawMessage: 'Call to method getTypeFromFunctionCall() of deprecated interface PHPStan\Type\FunctionParameterClosureTypeExtension.'
65+
identifier: method.deprecatedInterface
66+
count: 1
67+
path: src/Analyser/NodeScopeResolver.php
68+
69+
-
70+
rawMessage: 'Call to method getTypeFromMethodCall() of deprecated interface PHPStan\Type\MethodParameterClosureTypeExtension.'
71+
identifier: method.deprecatedInterface
72+
count: 1
73+
path: src/Analyser/NodeScopeResolver.php
74+
75+
-
76+
rawMessage: 'Call to method getTypeFromStaticMethodCall() of deprecated interface PHPStan\Type\StaticMethodParameterClosureTypeExtension.'
77+
identifier: method.deprecatedInterface
78+
count: 1
79+
path: src/Analyser/NodeScopeResolver.php
80+
81+
-
82+
rawMessage: 'Call to method isFunctionSupported() of deprecated interface PHPStan\Type\FunctionParameterClosureTypeExtension.'
83+
identifier: method.deprecatedInterface
84+
count: 1
85+
path: src/Analyser/NodeScopeResolver.php
86+
87+
-
88+
rawMessage: 'Call to method isMethodSupported() of deprecated interface PHPStan\Type\MethodParameterClosureTypeExtension.'
89+
identifier: method.deprecatedInterface
90+
count: 1
91+
path: src/Analyser/NodeScopeResolver.php
92+
93+
-
94+
rawMessage: 'Call to method isStaticMethodSupported() of deprecated interface PHPStan\Type\StaticMethodParameterClosureTypeExtension.'
95+
identifier: method.deprecatedInterface
96+
count: 1
97+
path: src/Analyser/NodeScopeResolver.php
98+
4599
-
46100
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
47101
identifier: phpstanApi.instanceofType
@@ -54,6 +108,18 @@ parameters:
54108
count: 1
55109
path: src/Analyser/NodeScopeResolver.php
56110

111+
-
112+
rawMessage: 'Parameter $parameterClosureTypeExtensionProvider of method PHPStan\Analyser\NodeScopeResolver::__construct() has typehint with deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider.'
113+
identifier: parameter.deprecatedInterface
114+
count: 1
115+
path: src/Analyser/NodeScopeResolver.php
116+
117+
-
118+
rawMessage: Property $parameterClosureTypeExtensionProvider references deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider in its type.
119+
identifier: property.deprecatedInterface
120+
count: 1
121+
path: src/Analyser/NodeScopeResolver.php
122+
57123
-
58124
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantBooleanType is error-prone and deprecated. Use Type::isTrue() or Type::isFalse() instead.'
59125
identifier: phpstanApi.instanceofType
@@ -216,6 +282,42 @@ parameters:
216282
count: 1
217283
path: src/DependencyInjection/NeonAdapter.php
218284

285+
-
286+
rawMessage: Access to constant on deprecated interface PHPStan\Type\FunctionParameterClosureTypeExtension.
287+
identifier: classConstant.deprecatedInterface
288+
count: 1
289+
path: src/DependencyInjection/ValidateServiceTagsExtension.php
290+
291+
-
292+
rawMessage: Access to constant on deprecated interface PHPStan\Type\MethodParameterClosureTypeExtension.
293+
identifier: classConstant.deprecatedInterface
294+
count: 1
295+
path: src/DependencyInjection/ValidateServiceTagsExtension.php
296+
297+
-
298+
rawMessage: Access to constant on deprecated interface PHPStan\Type\StaticMethodParameterClosureTypeExtension.
299+
identifier: classConstant.deprecatedInterface
300+
count: 1
301+
path: src/DependencyInjection/ValidateServiceTagsExtension.php
302+
303+
-
304+
rawMessage: Fetching class constant FUNCTION_TAG of deprecated class PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider.
305+
identifier: classConstant.deprecatedClass
306+
count: 1
307+
path: src/DependencyInjection/ValidateServiceTagsExtension.php
308+
309+
-
310+
rawMessage: Fetching class constant METHOD_TAG of deprecated class PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider.
311+
identifier: classConstant.deprecatedClass
312+
count: 1
313+
path: src/DependencyInjection/ValidateServiceTagsExtension.php
314+
315+
-
316+
rawMessage: Fetching class constant STATIC_METHOD_TAG of deprecated class PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider.
317+
identifier: classConstant.deprecatedClass
318+
count: 1
319+
path: src/DependencyInjection/ValidateServiceTagsExtension.php
320+
219321
-
220322
rawMessage: Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine. Use objects retrieved from ReflectionProvider instead.
221323
identifier: phpstanApi.runtimeReflection
@@ -771,6 +873,18 @@ parameters:
771873
count: 1
772874
path: src/Testing/PHPStanTestCase.php
773875

876+
-
877+
rawMessage: Access to constant on deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider.
878+
identifier: classConstant.deprecatedInterface
879+
count: 1
880+
path: src/Testing/RuleTestCase.php
881+
882+
-
883+
rawMessage: Access to constant on deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider.
884+
identifier: classConstant.deprecatedInterface
885+
count: 1
886+
path: src/Testing/TypeInferenceTestCase.php
887+
774888
-
775889
rawMessage: 'Doing instanceof PHPStan\Type\ConstantScalarType is error-prone and deprecated. Use Type::isConstantScalarValue() or Type::getConstantScalarTypes() or Type::getConstantScalarValues() instead.'
776890
identifier: phpstanApi.instanceofType
@@ -1653,6 +1767,12 @@ parameters:
16531767
count: 1
16541768
path: src/Type/Php/NumberFormatFunctionDynamicReturnTypeExtension.php
16551769

1770+
-
1771+
rawMessage: Class PHPStan\Type\Php\PregReplaceCallbackClosureTypeExtension implements deprecated interface PHPStan\Type\FunctionParameterClosureTypeExtension.
1772+
identifier: class.implementsDeprecatedInterface
1773+
count: 1
1774+
path: src/Type/Php/PregReplaceCallbackClosureTypeExtension.php
1775+
16561776
-
16571777
rawMessage: 'Doing instanceof PHPStan\Type\Constant\ConstantStringType is error-prone and deprecated. Use Type::getConstantStrings() instead.'
16581778
identifier: phpstanApi.instanceofType
@@ -1881,6 +2001,12 @@ parameters:
18812001
count: 2
18822002
path: src/Type/VoidType.php
18832003

2004+
-
2005+
rawMessage: Access to constant on deprecated interface PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider.
2006+
identifier: classConstant.deprecatedInterface
2007+
count: 1
2008+
path: tests/PHPStan/Analyser/AnalyserTest.php
2009+
18842010
-
18852011
rawMessage: Unreachable statement - code above always terminates.
18862012
identifier: deadCode.unreachable

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]
@@ -5313,7 +5315,8 @@ private function processArgs(
53135315
}
53145316

53155317
if ($parameter !== null) {
5316-
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
5318+
$overwritingParameterType = $this->getParameterTypeFromDynamicParameterTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass)
5319+
?? $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
53175320

53185321
if ($overwritingParameterType !== null) {
53195322
$parameterType = $overwritingParameterType;
@@ -5368,7 +5371,8 @@ private function processArgs(
53685371
}
53695372

53705373
if ($parameter !== null) {
5371-
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
5374+
$overwritingParameterType = $this->getParameterTypeFromDynamicParameterTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass)
5375+
?? $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
53725376

53735377
if ($overwritingParameterType !== null) {
53745378
$parameterType = $overwritingParameterType;
@@ -5517,6 +5521,46 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
55175521
return new ExpressionResult($scope, $hasYield, $isAlwaysTerminating, $throwPoints, $impurePoints);
55185522
}
55195523

5524+
/**
5525+
* @param MethodReflection|FunctionReflection|null $calleeReflection
5526+
*/
5527+
private function getParameterTypeFromDynamicParameterTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type
5528+
{
5529+
if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) {
5530+
foreach ($this->dynamicParameterTypeExtensionProvider->getDynamicFunctionParameterTypeExtensions() as $dynamicFunctionParameterTypeExtension) {
5531+
if (!$dynamicFunctionParameterTypeExtension->isFunctionSupported($calleeReflection, $parameter)) {
5532+
continue;
5533+
}
5534+
$type = $dynamicFunctionParameterTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope);
5535+
if ($type !== null) {
5536+
return $type;
5537+
}
5538+
}
5539+
} elseif ($callLike instanceof StaticCall && $calleeReflection instanceof MethodReflection) {
5540+
foreach ($this->dynamicParameterTypeExtensionProvider->getDynamicStaticMethodParameterTypeExtensions() as $dynamicStaticMethodParameterTypeExtension) {
5541+
if (!$dynamicStaticMethodParameterTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) {
5542+
continue;
5543+
}
5544+
$type = $dynamicStaticMethodParameterTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope);
5545+
if ($type !== null) {
5546+
return $type;
5547+
}
5548+
}
5549+
} elseif ($callLike instanceof MethodCall && $calleeReflection instanceof MethodReflection) {
5550+
foreach ($this->dynamicParameterTypeExtensionProvider->getDynamicMethodParameterTypeExtensions() as $dynamicMethodParameterTypeExtension) {
5551+
if (!$dynamicMethodParameterTypeExtension->isMethodSupported($calleeReflection, $parameter)) {
5552+
continue;
5553+
}
5554+
$type = $dynamicMethodParameterTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope);
5555+
if ($type !== null) {
5556+
return $type;
5557+
}
5558+
}
5559+
}
5560+
5561+
return null;
5562+
}
5563+
55205564
/**
55215565
* @param MethodReflection|FunctionReflection|null $calleeReflection
55225566
*/
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;
@@ -94,6 +98,9 @@ final class ValidateServiceTagsExtension extends CompilerExtension
9498
FunctionParameterClosureThisExtension::class => LazyParameterClosureThisExtensionProvider::FUNCTION_TAG,
9599
MethodParameterClosureThisExtension::class => LazyParameterClosureThisExtensionProvider::METHOD_TAG,
96100
StaticMethodParameterClosureThisExtension::class => LazyParameterClosureThisExtensionProvider::STATIC_METHOD_TAG,
101+
DynamicFunctionParameterTypeExtension::class => LazyDynamicParameterTypeExtensionProvider::FUNCTION_TAG,
102+
DynamicMethodParameterTypeExtension::class => LazyDynamicParameterTypeExtensionProvider::METHOD_TAG,
103+
DynamicStaticMethodParameterTypeExtension::class => LazyDynamicParameterTypeExtensionProvider::STATIC_METHOD_TAG,
97104
FunctionParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::FUNCTION_TAG,
98105
MethodParameterClosureTypeExtension::class => LazyParameterClosureTypeExtensionProvider::METHOD_TAG,
99106
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'),

0 commit comments

Comments
 (0)