Skip to content

Commit b308008

Browse files
schlndhondrejmirtes
authored andcommitted
WIP: apply parameter closure type extension in FunctionCallParametersCheck
1 parent 031f34e commit b308008

35 files changed

+515
-54
lines changed

conf/config.neon

+2
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,8 @@ services:
687687
-
688688
class: PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider
689689
factory: PHPStan\DependencyInjection\Type\LazyParameterClosureTypeExtensionProvider
690+
-
691+
class: PHPStan\Type\ParameterClosureTypeHelper
690692

691693
-
692694
class: PHPStan\File\FileHelper

src/Analyser/NodeScopeResolver.php

+4-34
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@
6363
use PHPStan\BetterReflection\SourceLocator\Located\LocatedSource;
6464
use PHPStan\DependencyInjection\Reflection\ClassReflectionExtensionRegistryProvider;
6565
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
66-
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
6766
use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider;
6867
use PHPStan\File\FileHelper;
6968
use PHPStan\File\FileReader;
@@ -172,6 +171,7 @@
172171
use PHPStan\Type\NullType;
173172
use PHPStan\Type\ObjectType;
174173
use PHPStan\Type\ObjectWithoutClassType;
174+
use PHPStan\Type\ParameterClosureTypeHelper;
175175
use PHPStan\Type\ResourceType;
176176
use PHPStan\Type\StaticType;
177177
use PHPStan\Type\StaticTypeFactory;
@@ -251,7 +251,7 @@ public function __construct(
251251
private readonly TypeSpecifier $typeSpecifier,
252252
private readonly DynamicThrowTypeExtensionProvider $dynamicThrowTypeExtensionProvider,
253253
private readonly ReadWritePropertiesExtensionProvider $readWritePropertiesExtensionProvider,
254-
private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider,
254+
private readonly ParameterClosureTypeHelper $parameterClosureTypeHelper,
255255
private readonly ScopeFactory $scopeFactory,
256256
private readonly bool $polluteScopeWithLoopInitialAssignments,
257257
private readonly bool $polluteScopeWithAlwaysIterableForeach,
@@ -4677,7 +4677,7 @@ private function processArgs(
46774677
}
46784678

46794679
if ($parameter !== null) {
4680-
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
4680+
$overwritingParameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
46814681

46824682
if ($overwritingParameterType !== null) {
46834683
$parameterType = $overwritingParameterType;
@@ -4729,7 +4729,7 @@ private function processArgs(
47294729
}
47304730

47314731
if ($parameter !== null) {
4732-
$overwritingParameterType = $this->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
4732+
$overwritingParameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension($callLike, $calleeReflection, $parameter, $scopeToPass);
47334733

47344734
if ($overwritingParameterType !== null) {
47354735
$parameterType = $overwritingParameterType;
@@ -4876,36 +4876,6 @@ static function (Node $node, Scope $scope) use ($nodeCallback): void {
48764876
return new ExpressionResult($scope, $hasYield, $throwPoints, $impurePoints);
48774877
}
48784878

4879-
/**
4880-
* @param MethodReflection|FunctionReflection|null $calleeReflection
4881-
*/
4882-
private function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, MutatingScope $scope): ?Type
4883-
{
4884-
if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) {
4885-
foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) {
4886-
if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) {
4887-
return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope);
4888-
}
4889-
}
4890-
} elseif ($calleeReflection instanceof MethodReflection) {
4891-
if ($callLike instanceof StaticCall) {
4892-
foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) {
4893-
if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) {
4894-
return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope);
4895-
}
4896-
}
4897-
} elseif ($callLike instanceof MethodCall) {
4898-
foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) {
4899-
if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) {
4900-
return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope);
4901-
}
4902-
}
4903-
}
4904-
}
4905-
4906-
return null;
4907-
}
4908-
49094879
/**
49104880
* @param MethodReflection|FunctionReflection|null $calleeReflection
49114881
*/

src/Rules/AttributesCheck.php

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ public function check(
155155
],
156156
'attribute',
157157
$attributeConstructor->acceptsNamedArguments(),
158+
$attributeConstructor,
158159
);
159160

160161
foreach ($parameterErrors as $error) {

src/Rules/Classes/InstantiationRule.php

+1
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ private function checkClassName(string $class, bool $isName, Node $node, Scope $
220220
],
221221
'new',
222222
$constructorReflection->acceptsNamedArguments(),
223+
$constructorReflection,
223224
));
224225
}
225226

src/Rules/FunctionCallParametersCheck.php

+13-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use PHPStan\Analyser\MutatingScope;
88
use PHPStan\Analyser\Scope;
99
use PHPStan\Php\PhpVersion;
10+
use PHPStan\Reflection\FunctionReflection;
11+
use PHPStan\Reflection\MethodReflection;
1012
use PHPStan\Reflection\ParameterReflection;
1113
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
1214
use PHPStan\Reflection\ParametersAcceptor;
@@ -20,6 +22,7 @@
2022
use PHPStan\Type\Generic\TemplateType;
2123
use PHPStan\Type\IntegerRangeType;
2224
use PHPStan\Type\NeverType;
25+
use PHPStan\Type\ParameterClosureTypeHelper;
2326
use PHPStan\Type\Type;
2427
use PHPStan\Type\TypeCombinator;
2528
use PHPStan\Type\TypeTraverser;
@@ -42,6 +45,7 @@ public function __construct(
4245
private PhpVersion $phpVersion,
4346
private UnresolvableTypeHelper $unresolvableTypeHelper,
4447
private PropertyReflectionFinder $propertyReflectionFinder,
48+
private ParameterClosureTypeHelper $parameterClosureTypeHelper,
4549
private bool $checkArgumentTypes,
4650
private bool $checkArgumentsPassedByReference,
4751
private bool $checkExtraArguments,
@@ -55,6 +59,7 @@ public function __construct(
5559
* @param Node\Expr\FuncCall|Node\Expr\MethodCall|Node\Expr\StaticCall|Node\Expr\New_ $funcCall
5660
* @param array{0: string, 1: string, 2: string, 3: string, 4: string, 5: string, 6: string, 7: string, 8: string, 9: string, 10: string, 11: string, 12: string, 13?: string, 14?: string} $messages
5761
* @param 'attribute'|'callable'|'method'|'staticMethod'|'function'|'new' $nodeType
62+
* @param MethodReflection|FunctionReflection|null $callReflection
5863
* @return list<IdentifierRuleError>
5964
*/
6065
public function check(
@@ -65,6 +70,7 @@ public function check(
6570
array $messages,
6671
string $nodeType = 'function',
6772
bool $acceptsNamedArguments = true,
73+
$callReflection = null,
6874
): array
6975
{
7076
$functionParametersMinCount = 0;
@@ -309,7 +315,13 @@ public function check(
309315
}
310316

311317
if ($this->checkArgumentTypes) {
312-
$parameterType = TypeUtils::resolveLateResolvableTypes($parameter->getType());
318+
$parameterType = $this->parameterClosureTypeHelper->getParameterTypeFromParameterClosureTypeExtension(
319+
$funcCall,
320+
$callReflection,
321+
$parameter,
322+
$scope,
323+
) ?? $parameter->getType();
324+
$parameterType = TypeUtils::resolveLateResolvableTypes($parameterType);
313325

314326
if (
315327
!$parameter->passedByReference()->createsNewVariable()

src/Rules/Functions/CallToFunctionParametersRule.php

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public function processNode(Node $node, Scope $scope): array
6868
],
6969
'function',
7070
$function->acceptsNamedArguments(),
71+
$function,
7172
);
7273
}
7374

src/Rules/Methods/CallMethodsRule.php

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public function processNode(Node $node, Scope $scope): array
7474
],
7575
'method',
7676
$methodReflection->acceptsNamedArguments(),
77+
$methodReflection,
7778
));
7879
}
7980

src/Rules/Methods/CallStaticMethodsRule.php

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public function processNode(Node $node, Scope $scope): array
8282
],
8383
'staticMethod',
8484
$method->acceptsNamedArguments(),
85+
$method,
8586
));
8687

8788
return $errors;

src/Testing/RuleTestCase.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use PHPStan\Collectors\Registry as CollectorRegistry;
1717
use PHPStan\Dependency\DependencyResolver;
1818
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
19-
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
2019
use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider;
2120
use PHPStan\File\FileHelper;
2221
use PHPStan\Php\PhpVersion;
@@ -30,6 +29,7 @@
3029
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
3130
use PHPStan\Rules\Rule;
3231
use PHPStan\Type\FileTypeMapper;
32+
use PHPStan\Type\ParameterClosureTypeHelper;
3333
use function array_map;
3434
use function count;
3535
use function implode;
@@ -95,7 +95,7 @@ private function getAnalyser(DirectRuleRegistry $ruleRegistry): Analyser
9595
$typeSpecifier,
9696
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
9797
$readWritePropertiesExtensions !== [] ? new DirectReadWritePropertiesExtensionProvider($readWritePropertiesExtensions) : self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
98-
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
98+
self::getContainer()->getByType(ParameterClosureTypeHelper::class),
9999
self::createScopeFactory($reflectionProvider, $typeSpecifier),
100100
$this->shouldPolluteScopeWithLoopInitialAssignments(),
101101
$this->shouldPolluteScopeWithAlwaysIterableForeach(),

src/Testing/TypeInferenceTestCase.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use PHPStan\Analyser\Scope;
1010
use PHPStan\Analyser\ScopeContext;
1111
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
12-
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
1312
use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider;
1413
use PHPStan\File\FileHelper;
1514
use PHPStan\File\SystemAgnosticSimpleRelativePathHelper;
@@ -22,6 +21,7 @@
2221
use PHPStan\TrinaryLogic;
2322
use PHPStan\Type\ConstantScalarType;
2423
use PHPStan\Type\FileTypeMapper;
24+
use PHPStan\Type\ParameterClosureTypeHelper;
2525
use PHPStan\Type\Type;
2626
use PHPStan\Type\VerbosityLevel;
2727
use Symfony\Component\Finder\Finder;
@@ -75,7 +75,7 @@ public static function processFile(
7575
$typeSpecifier,
7676
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
7777
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
78-
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
78+
self::getContainer()->getByType(ParameterClosureTypeHelper::class),
7979
self::createScopeFactory($reflectionProvider, $typeSpecifier),
8080
self::getContainer()->getParameter('polluteScopeWithLoopInitialAssignments'),
8181
self::getContainer()->getParameter('polluteScopeWithAlwaysIterableForeach'),
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type;
4+
5+
use PhpParser\Node\Expr\CallLike;
6+
use PhpParser\Node\Expr\FuncCall;
7+
use PhpParser\Node\Expr\MethodCall;
8+
use PhpParser\Node\Expr\StaticCall;
9+
use PHPStan\Analyser\Scope;
10+
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
11+
use PHPStan\Reflection\FunctionReflection;
12+
use PHPStan\Reflection\MethodReflection;
13+
use PHPStan\Reflection\ParameterReflection;
14+
15+
final class ParameterClosureTypeHelper
16+
{
17+
18+
public function __construct(private readonly ParameterClosureTypeExtensionProvider $parameterClosureTypeExtensionProvider)
19+
{
20+
}
21+
22+
/**
23+
* @param MethodReflection|FunctionReflection|null $calleeReflection
24+
*/
25+
public function getParameterTypeFromParameterClosureTypeExtension(CallLike $callLike, $calleeReflection, ParameterReflection $parameter, Scope $scope): ?Type
26+
{
27+
if ($callLike instanceof FuncCall && $calleeReflection instanceof FunctionReflection) {
28+
foreach ($this->parameterClosureTypeExtensionProvider->getFunctionParameterClosureTypeExtensions() as $functionParameterClosureTypeExtension) {
29+
if ($functionParameterClosureTypeExtension->isFunctionSupported($calleeReflection, $parameter)) {
30+
return $functionParameterClosureTypeExtension->getTypeFromFunctionCall($calleeReflection, $callLike, $parameter, $scope);
31+
}
32+
}
33+
} elseif ($calleeReflection instanceof MethodReflection) {
34+
if ($callLike instanceof StaticCall) {
35+
foreach ($this->parameterClosureTypeExtensionProvider->getStaticMethodParameterClosureTypeExtensions() as $staticMethodParameterClosureTypeExtension) {
36+
if ($staticMethodParameterClosureTypeExtension->isStaticMethodSupported($calleeReflection, $parameter)) {
37+
return $staticMethodParameterClosureTypeExtension->getTypeFromStaticMethodCall($calleeReflection, $callLike, $parameter, $scope);
38+
}
39+
}
40+
} elseif ($callLike instanceof MethodCall) {
41+
foreach ($this->parameterClosureTypeExtensionProvider->getMethodParameterClosureTypeExtensions() as $methodParameterClosureTypeExtension) {
42+
if ($methodParameterClosureTypeExtension->isMethodSupported($calleeReflection, $parameter)) {
43+
return $methodParameterClosureTypeExtension->getTypeFromMethodCall($calleeReflection, $callLike, $parameter, $scope);
44+
}
45+
}
46+
}
47+
}
48+
49+
return null;
50+
}
51+
52+
}

src/Type/Php/PregReplaceCallbackClosureTypeExtension.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use PHPStan\Reflection\Native\NativeParameterReflection;
99
use PHPStan\Reflection\ParameterReflection;
1010
use PHPStan\TrinaryLogic;
11-
use PHPStan\Type\ClosureType;
11+
use PHPStan\Type\CallableType;
1212
use PHPStan\Type\FunctionParameterClosureTypeExtension;
1313
use PHPStan\Type\StringType;
1414
use PHPStan\Type\Type;
@@ -49,7 +49,7 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection,
4949
return null;
5050
}
5151

52-
return new ClosureType(
52+
return new CallableType(
5353
[
5454
new NativeParameterReflection($parameter->getName(), $parameter->isOptional(), $matchesType, $parameter->passedByReference(), $parameter->isVariadic(), $parameter->getDefaultValue()),
5555
],

tests/PHPStan/Analyser/AnalyserTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use PHPStan\Dependency\DependencyResolver;
1212
use PHPStan\Dependency\ExportedNodeResolver;
1313
use PHPStan\DependencyInjection\Type\DynamicThrowTypeExtensionProvider;
14-
use PHPStan\DependencyInjection\Type\ParameterClosureTypeExtensionProvider;
1514
use PHPStan\DependencyInjection\Type\ParameterOutTypeExtensionProvider;
1615
use PHPStan\Node\Printer\ExprPrinter;
1716
use PHPStan\Node\Printer\Printer;
@@ -26,6 +25,7 @@
2625
use PHPStan\Rules\Properties\ReadWritePropertiesExtensionProvider;
2726
use PHPStan\Testing\PHPStanTestCase;
2827
use PHPStan\Type\FileTypeMapper;
28+
use PHPStan\Type\ParameterClosureTypeHelper;
2929
use stdClass;
3030
use function array_map;
3131
use function array_merge;
@@ -731,7 +731,7 @@ private function createAnalyser(bool $enableIgnoreErrorsWithinPhpDocs): Analyser
731731
$typeSpecifier,
732732
self::getContainer()->getByType(DynamicThrowTypeExtensionProvider::class),
733733
self::getContainer()->getByType(ReadWritePropertiesExtensionProvider::class),
734-
self::getContainer()->getByType(ParameterClosureTypeExtensionProvider::class),
734+
self::getContainer()->getByType(ParameterClosureTypeHelper::class),
735735
self::createScopeFactory($reflectionProvider, $typeSpecifier),
736736
false,
737737
true,

tests/PHPStan/Analyser/Bug9307CallMethodsRuleTest.php

+14-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\Rules\Rule;
1313
use PHPStan\Rules\RuleLevelHelper;
1414
use PHPStan\Testing\RuleTestCase;
15+
use PHPStan\Type\ParameterClosureTypeHelper;
1516
use const PHP_VERSION_ID;
1617

1718
/**
@@ -26,7 +27,19 @@ protected function getRule(): Rule
2627
$ruleLevelHelper = new RuleLevelHelper($reflectionProvider, true, false, true, true, false, true, false);
2728
return new CallMethodsRule(
2829
new MethodCallCheck($reflectionProvider, $ruleLevelHelper, true, true),
29-
new FunctionCallParametersCheck($ruleLevelHelper, new NullsafeCheck(), new PhpVersion(PHP_VERSION_ID), new UnresolvableTypeHelper(), new PropertyReflectionFinder(), true, true, true, true, true),
30+
new FunctionCallParametersCheck(
31+
$ruleLevelHelper,
32+
new NullsafeCheck(),
33+
new PhpVersion(PHP_VERSION_ID),
34+
new UnresolvableTypeHelper(),
35+
new PropertyReflectionFinder(),
36+
self::getContainer()->getByType(ParameterClosureTypeHelper::class),
37+
true,
38+
true,
39+
true,
40+
true,
41+
true,
42+
),
3043
);
3144
}
3245

tests/PHPStan/Rules/Classes/ClassAttributesRuleTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\Rules\Rule;
1515
use PHPStan\Rules\RuleLevelHelper;
1616
use PHPStan\Testing\RuleTestCase;
17+
use PHPStan\Type\ParameterClosureTypeHelper;
1718
use const PHP_VERSION_ID;
1819

1920
/**
@@ -38,6 +39,7 @@ protected function getRule(): Rule
3839
new PhpVersion(80000),
3940
new UnresolvableTypeHelper(),
4041
new PropertyReflectionFinder(),
42+
self::getContainer()->getByType(ParameterClosureTypeHelper::class),
4143
true,
4244
true,
4345
true,

tests/PHPStan/Rules/Classes/ClassConstantAttributesRuleTest.php

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use PHPStan\Rules\Rule;
1515
use PHPStan\Rules\RuleLevelHelper;
1616
use PHPStan\Testing\RuleTestCase;
17+
use PHPStan\Type\ParameterClosureTypeHelper;
1718

1819
/**
1920
* @extends RuleTestCase<ClassConstantAttributesRule>
@@ -33,6 +34,7 @@ protected function getRule(): Rule
3334
new PhpVersion(80000),
3435
new UnresolvableTypeHelper(),
3536
new PropertyReflectionFinder(),
37+
self::getContainer()->getByType(ParameterClosureTypeHelper::class),
3638
true,
3739
true,
3840
true,

0 commit comments

Comments
 (0)