Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b01e592

Browse files
Add ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension (#35)
* Add ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension * Add test on variadic arguments * Apply review * Reflected `__construct` should have object as return type
1 parent f4b1d05 commit b01e592

File tree

5 files changed

+428
-0
lines changed

5 files changed

+428
-0
lines changed

‎docs/type-inference.md‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Type Inference
2+
3+
All type inference capabilities of this extension are summarised below:
4+
5+
## Dynamic Static Method Return Type Extensions
6+
7+
### ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension
8+
9+
This extension provides precise return type to `ReflectionHelper`'s static `getPrivateMethodInvoker()` method.
10+
Since PHPStan's dynamic return type extensions work on classes, not traits, this extension is on by default
11+
in test cases extending `CodeIgniter\Test\CIUnitTestCase`. To make this work, you should be calling the method
12+
**statically**:
13+
14+
For example, we're accessing the private method:
15+
```php
16+
class Foo
17+
{
18+
private static function privateMethod(string $value): bool
19+
{
20+
return true;
21+
}
22+
}
23+
```
24+
25+
**Before**
26+
```php
27+
public function testSomePrivateMethod(): void
28+
{
29+
$method = self::getPrivateMethodInvoker(new Foo(), 'privateMethod');
30+
\PHPStan\dumpType($method); // Closure(mixed ...): mixed
31+
}
32+
33+
```
34+
35+
**After**
36+
```php
37+
public function testSomePrivateMethod(): void
38+
{
39+
$method = self::getPrivateMethodInvoker(new Foo(), 'privateMethod');
40+
\PHPStan\dumpType($method); // Closure(string): bool
41+
}
42+
43+
```
44+
45+
> [!NOTE]
46+
>
47+
> If you are using `ReflectionHelper` outside of testing, you can still enjoy the precise return types by adding a
48+
> service for the class using this trait. In your `phpstan.neon` (or `phpstan.neon.dist`), add the following to
49+
> the _**services**_ schema:
50+
>
51+
> ```yml
52+
> -
53+
> class: CodeIgniter\PHPStan\Type\ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension
54+
> tags:
55+
> - phpstan.broker.dynamicStaticMethodReturnTypeExtension
56+
> arguments:
57+
> class: <Fully qualified class name of class using ReflectionHelper>
58+
>
59+
> ```

‎extension.neon‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,14 @@ services:
7777
tags:
7878
- phpstan.broker.dynamicMethodReturnTypeExtension
7979

80+
# DynamicStaticMethodReturnTypeExtension
81+
-
82+
class: CodeIgniter\PHPStan\Type\ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension
83+
tags:
84+
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
85+
arguments:
86+
class: CodeIgniter\Test\CIUnitTestCase
87+
8088
# conditional rules
8189
-
8290
class: CodeIgniter\PHPStan\Rules\Functions\FactoriesFunctionArgumentTypeRule
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\PHPStan\Type;
15+
16+
use PhpParser\Node\Expr\StaticCall;
17+
use PHPStan\Analyser\Scope;
18+
use PHPStan\Reflection\MethodReflection;
19+
use PHPStan\Reflection\ParametersAcceptorSelector;
20+
use PHPStan\Type\ClosureType;
21+
use PHPStan\Type\DynamicStaticMethodReturnTypeExtension;
22+
use PHPStan\Type\IntersectionType;
23+
use PHPStan\Type\NeverType;
24+
use PHPStan\Type\Type;
25+
use PHPStan\Type\TypeCombinator;
26+
use PHPStan\Type\TypeTraverser;
27+
use PHPStan\Type\UnionType;
28+
29+
final class ReflectionHelperGetPrivateMethodInvokerReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension
30+
{
31+
/**
32+
* @param class-string $class
33+
*/
34+
public function __construct(
35+
private readonly string $class,
36+
) {}
37+
38+
public function getClass(): string
39+
{
40+
return $this->class;
41+
}
42+
43+
public function isStaticMethodSupported(MethodReflection $methodReflection): bool
44+
{
45+
return $methodReflection->getName() === 'getPrivateMethodInvoker';
46+
}
47+
48+
public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, StaticCall $methodCall, Scope $scope): ?Type
49+
{
50+
$args = $methodCall->getArgs();
51+
52+
if (count($args) !== 2) {
53+
return null;
54+
}
55+
56+
$objectType = $scope->getType($args[0]->value)->getObjectTypeOrClassStringObjectType();
57+
$methodType = $scope->getType($args[1]->value);
58+
59+
if (! $objectType->isObject()->yes()) {
60+
return new NeverType(true);
61+
}
62+
63+
return TypeTraverser::map($objectType, static function (Type $type, callable $traverse) use ($methodType, $scope, $args, $methodReflection): Type {
64+
if ($type instanceof UnionType || $type instanceof IntersectionType) {
65+
return $traverse($type);
66+
}
67+
68+
$closures = [];
69+
70+
foreach ($type->getObjectClassReflections() as $classReflection) {
71+
foreach ($methodType->getConstantStrings() as $methodStringType) {
72+
$methodName = $methodStringType->getValue();
73+
74+
if (! $classReflection->hasMethod($methodName)) {
75+
$closures[] = new NeverType(true);
76+
77+
continue;
78+
}
79+
80+
$invokedMethodReflection = $classReflection->getMethod($methodName, $scope);
81+
82+
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
83+
$scope,
84+
[],
85+
$invokedMethodReflection->getVariants(),
86+
$invokedMethodReflection->getNamedArgumentsVariants(),
87+
);
88+
89+
$returnType = strtolower($methodName) === '__construct' ? $type : $parametersAcceptor->getReturnType();
90+
91+
$closures[] = new ClosureType(
92+
$parametersAcceptor->getParameters(),
93+
$returnType,
94+
$parametersAcceptor->isVariadic(),
95+
$parametersAcceptor->getTemplateTypeMap(),
96+
$parametersAcceptor->getResolvedTemplateTypeMap(),
97+
);
98+
}
99+
}
100+
101+
if ($closures === []) {
102+
return ParametersAcceptorSelector::selectFromArgs(
103+
$scope,
104+
$args,
105+
$methodReflection->getVariants(),
106+
)->getReturnType();
107+
}
108+
109+
return TypeCombinator::union(...$closures);
110+
});
111+
}
112+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) 2023 CodeIgniter Foundation <admin@codeigniter.com>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\PHPStan\Tests\Type;
15+
16+
use CodeIgniter\PHPStan\Tests\AdditionalConfigFilesTrait;
17+
use PHPStan\Testing\TypeInferenceTestCase;
18+
use PHPUnit\Framework\Attributes\DataProvider;
19+
use PHPUnit\Framework\Attributes\Group;
20+
21+
/**
22+
* @internal
23+
*/
24+
#[Group('Integration')]
25+
final class DynamicStaticMethodReturnTypeExtensionTest extends TypeInferenceTestCase
26+
{
27+
use AdditionalConfigFilesTrait;
28+
29+
#[DataProvider('provideFileAssertsCases')]
30+
public function testFileAsserts(string $assertType, string $file, mixed ...$args): void
31+
{
32+
$this->assertFileAsserts($assertType, $file, ...$args);
33+
}
34+
35+
/**
36+
* @return iterable<string, array<array-key, mixed>>
37+
*/
38+
public static function provideFileAssertsCases(): iterable
39+
{
40+
yield from self::gatherAssertTypes(__DIR__ . '/data/reflection-helper.php');
41+
}
42+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /