Skip to content

Commit 31a5668

Browse files
committed
Fix false positive
1 parent a6d87e0 commit 31a5668

7 files changed

+192
-7
lines changed

extension.neon

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,13 @@ services:
6969
factory: PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension
7070
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
7171

72-
# Request::getSession() type specification
72+
# Request::getSession() type specification and return type
7373
-
7474
factory: PHPStan\Type\Symfony\RequestTypeSpecifyingExtension
7575
tags: [phpstan.typeSpecifier.methodTypeSpecifyingExtension]
76+
-
77+
factory: PHPStan\Type\Symfony\RequestGetSessionDynamicReturnTypeExtension
78+
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
7679

7780
# HeaderBag::get() return type
7881
-
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PhpParser\Node\Expr\MethodCall;
6+
use PhpParser\PrettyPrinter\Standard;
7+
use PHPStan\Analyser\Scope;
8+
use PHPStan\Reflection\MethodReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\Type\BooleanType;
11+
use PHPStan\Type\DynamicMethodReturnTypeExtension;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeCombinator;
14+
15+
final class RequestGetSessionDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
16+
{
17+
18+
/** @var \PhpParser\PrettyPrinter\Standard */
19+
private $printer;
20+
21+
public function __construct(Standard $printer)
22+
{
23+
$this->printer = $printer;
24+
}
25+
26+
public function getClass(): string
27+
{
28+
return 'Symfony\Component\HttpFoundation\Request';
29+
}
30+
31+
public function isMethodSupported(MethodReflection $methodReflection): bool
32+
{
33+
return $methodReflection->getName() === 'getSession';
34+
}
35+
36+
public function getTypeFromMethodCall(
37+
MethodReflection $methodReflection,
38+
MethodCall $methodCall,
39+
Scope $scope
40+
): Type
41+
{
42+
$defaultReturnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();
43+
$defaultReturnTypeWithoutNull = TypeCombinator::removeNull($defaultReturnType);
44+
45+
if ($scope->getType(Helper::createMarkerNode($methodCall, $defaultReturnTypeWithoutNull, $this->printer))->equals(new BooleanType())) {
46+
return $defaultReturnTypeWithoutNull;
47+
}
48+
49+
return $defaultReturnType;
50+
}
51+
52+
}

src/Type/Symfony/RequestTypeSpecifyingExtension.php

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PHPStan\Type\Symfony;
44

55
use PhpParser\Node\Expr\MethodCall;
6+
use PhpParser\PrettyPrinter\Standard;
67
use PHPStan\Analyser\Scope;
78
use PHPStan\Analyser\SpecifiedTypes;
89
use PHPStan\Analyser\TypeSpecifier;
@@ -11,14 +12,14 @@
1112
use PHPStan\Broker\Broker;
1213
use PHPStan\Reflection\MethodReflection;
1314
use PHPStan\Reflection\ParametersAcceptorSelector;
15+
use PHPStan\Type\BooleanType;
1416
use PHPStan\Type\MethodTypeSpecifyingExtension;
1517
use PHPStan\Type\TypeCombinator;
1618

1719
final class RequestTypeSpecifyingExtension implements MethodTypeSpecifyingExtension, TypeSpecifierAwareExtension
1820
{
1921

2022
private const REQUEST_CLASS = 'Symfony\Component\HttpFoundation\Request';
21-
private const HAS_METHOD_NAME = 'hasSession';
2223
private const GET_METHOD_NAME = 'getSession';
2324

2425
/** @var Broker */
@@ -27,9 +28,13 @@ final class RequestTypeSpecifyingExtension implements MethodTypeSpecifyingExtens
2728
/** @var TypeSpecifier */
2829
private $typeSpecifier;
2930

30-
public function __construct(Broker $broker)
31+
/** @var \PhpParser\PrettyPrinter\Standard */
32+
private $printer;
33+
34+
public function __construct(Broker $broker, Standard $printer)
3135
{
3236
$this->broker = $broker;
37+
$this->printer = $printer;
3338
}
3439

3540
public function getClass(): string
@@ -39,7 +44,7 @@ public function getClass(): string
3944

4045
public function isMethodSupported(MethodReflection $methodReflection, MethodCall $node, TypeSpecifierContext $context): bool
4146
{
42-
return $methodReflection->getName() === self::HAS_METHOD_NAME && !$context->null();
47+
return $methodReflection->getName() === 'hasSession' && !$context->null();
4348
}
4449

4550
public function specifyTypes(MethodReflection $methodReflection, MethodCall $node, Scope $scope, TypeSpecifierContext $context): SpecifiedTypes
@@ -48,8 +53,12 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod
4853
$methodVariants = $classReflection->getNativeMethod(self::GET_METHOD_NAME)->getVariants();
4954

5055
return $this->typeSpecifier->create(
51-
new MethodCall($node->var, self::GET_METHOD_NAME),
52-
TypeCombinator::removeNull(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType()),
56+
Helper::createMarkerNode(
57+
new MethodCall($node->var, self::GET_METHOD_NAME),
58+
TypeCombinator::removeNull(ParametersAcceptorSelector::selectSingle($methodVariants)->getReturnType()),
59+
$this->printer
60+
),
61+
new BooleanType(),
5362
$context
5463
);
5564
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use Iterator;
6+
use PhpParser\PrettyPrinter\Standard;
7+
use PHPStan\Type\MethodTypeSpecifyingExtension;
8+
use ReflectionMethod;
9+
use Symfony\Component\HttpFoundation\Request;
10+
use Symfony\Component\HttpFoundation\Session\SessionInterface;
11+
use function strpos;
12+
13+
final class RequestGetSessionDynamicReturnTypeExtensionTest extends ExtensionTestCase
14+
{
15+
16+
/**
17+
* @dataProvider getContentProvider
18+
*/
19+
public function testGetContent(string $expression, string $type): void
20+
{
21+
$this->processFile(
22+
__DIR__ . '/request_get_session.php',
23+
$expression,
24+
$type,
25+
new RequestGetSessionDynamicReturnTypeExtension(new Standard())
26+
);
27+
}
28+
29+
/** @return MethodTypeSpecifyingExtension[] */
30+
protected function getMethodTypeSpecifyingExtensions(): array
31+
{
32+
return [
33+
new RequestTypeSpecifyingExtension($this->createBroker(), new Standard()),
34+
];
35+
}
36+
37+
/**
38+
* @return Iterator<int, array{string, string}>
39+
*/
40+
public function getContentProvider(): Iterator
41+
{
42+
$ref = new ReflectionMethod(Request::class, 'getSession');
43+
$doc = (string) $ref->getDocComment();
44+
45+
$checkedTypeString = SessionInterface::class;
46+
if (strpos($doc, '@return SessionInterface|null') !== false) {
47+
$checkedTypeString .= '|null';
48+
}
49+
50+
yield ['$session1', $checkedTypeString];
51+
yield ['$session2', SessionInterface::class];
52+
}
53+
54+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Symfony;
4+
5+
use PhpParser\PrettyPrinter\Standard;
6+
use PHPStan\Broker\Broker;
7+
use PHPStan\Rules\Comparison\ImpossibleCheckTypeHelper;
8+
use PHPStan\Rules\Comparison\ImpossibleCheckTypeMethodCallRule;
9+
use PHPStan\Rules\Rule;
10+
use PHPStan\Testing\RuleTestCase;
11+
use PHPStan\Type\MethodTypeSpecifyingExtension;
12+
13+
/**
14+
* @extends RuleTestCase<\PHPStan\Rules\Comparison\ImpossibleCheckTypeMethodCallRule>
15+
*/
16+
final class RequestTypeSpecifyingExtensionImpossibleTest extends RuleTestCase
17+
{
18+
19+
/** @var \PHPStan\Broker\Broker|null */
20+
private $broker;
21+
22+
/** @var \PHPStan\Type\MethodTypeSpecifyingExtension|null */
23+
private $extension;
24+
25+
private function getBroker(): Broker
26+
{
27+
if ($this->broker === null) {
28+
$this->broker = $this->createBroker();
29+
}
30+
return $this->broker;
31+
}
32+
33+
private function getExtension(): MethodTypeSpecifyingExtension
34+
{
35+
if ($this->extension === null) {
36+
$this->extension = new RequestTypeSpecifyingExtension($this->getBroker(), new Standard());
37+
}
38+
return $this->extension;
39+
}
40+
41+
protected function getRule(): Rule
42+
{
43+
return new ImpossibleCheckTypeMethodCallRule(new ImpossibleCheckTypeHelper(
44+
$this->getBroker(),
45+
$this->createTypeSpecifier(new Standard(), $this->getBroker(), $this->getMethodTypeSpecifyingExtensions(), []),
46+
[],
47+
false
48+
), true, false);
49+
}
50+
51+
/** @return MethodTypeSpecifyingExtension[] */
52+
protected function getMethodTypeSpecifyingExtensions(): array
53+
{
54+
return [
55+
$this->getExtension(),
56+
];
57+
}
58+
59+
public function testGetSession(): void
60+
{
61+
$this->analyse([__DIR__ . '/request_get_session.php'], []);
62+
}
63+
64+
}

tests/Type/Symfony/RequestTypeSpecifyingExtensionTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PHPStan\Type\Symfony;
44

5+
use PhpParser\PrettyPrinter\Standard;
56
use PHPStan\Rules\Rule;
67
use PHPStan\Testing\RuleTestCase;
78
use PHPStan\Type\MethodTypeSpecifyingExtension;
@@ -25,7 +26,7 @@ protected function getRule(): Rule
2526
protected function getMethodTypeSpecifyingExtensions(): array
2627
{
2728
return [
28-
new RequestTypeSpecifyingExtension($this->createBroker()),
29+
new RequestTypeSpecifyingExtension($this->createBroker(), new Standard()),
2930
];
3031
}
3132

tests/Type/Symfony/request_get_session.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@
1010
$session2 = $request->getSession();
1111
$session2;
1212
}
13+
14+
die;

0 commit comments

Comments
 (0)