Skip to content

Commit fe750c5

Browse files
Declare Memcached methods as impure (#5536)
1 parent 3103ea4 commit fe750c5

9 files changed

Lines changed: 136 additions & 5 deletions

File tree

conf/config.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ parameters:
125125
universalObjectCratesClasses:
126126
- stdClass
127127
stubFiles:
128+
- ../stubs/Memcached.stub
128129
- ../stubs/Redis.stub
129130
- ../stubs/ReflectionAttribute.stub
130131
- ../stubs/ReflectionClassConstant.stub

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,12 @@ private function createMethod(
618618
$acceptsNamedArguments = true;
619619
$selfOutType = null;
620620
$phpDocComment = null;
621+
622+
$isPure = null;
623+
if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) {
624+
$isPure = !$this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects'];
625+
}
626+
621627
$methodSignaturesResult = $this->signatureMapProvider->getMethodSignatures($declaringClassName, $methodReflection->getName(), $methodReflection);
622628
foreach ($methodSignaturesResult as $signatureType => $methodSignatures) {
623629
if ($methodSignatures === null) {
@@ -693,6 +699,7 @@ private function createMethod(
693699

694700
$asserts = Assertions::createFromResolvedPhpDocBlock($currentResolvedPhpDoc);
695701
$acceptsNamedArguments = $currentResolvedPhpDoc->acceptsNamedArguments();
702+
$isPure ??= $currentResolvedPhpDoc->isPure();
696703

697704
$selfOutTypeTag = $currentResolvedPhpDoc->getSelfOutTag();
698705
if ($selfOutTypeTag !== null) {
@@ -727,19 +734,23 @@ private function createMethod(
727734
}
728735
}
729736

730-
if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) {
731-
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']);
732-
} else {
733-
$hasSideEffects = TrinaryLogic::createMaybe();
737+
if ($isPure === null) {
738+
$classResolvedPhpDoc = $declaringClass->getResolvedPhpDoc();
739+
if ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsPure()) {
740+
$isPure = true;
741+
} elseif ($classResolvedPhpDoc !== null && $classResolvedPhpDoc->areAllMethodsImpure()) {
742+
$isPure = false;
743+
}
734744
}
745+
735746
return new NativeMethodReflection(
736747
$this->reflectionProviderProvider->getReflectionProvider(),
737748
$declaringClass,
738749
$methodReflection,
739750
$currentResolvedPhpDoc ?? null,
740751
$variantsByType['positional'],
741752
$variantsByType['named'] ?? null,
742-
$hasSideEffects,
753+
$isPure !== null ? TrinaryLogic::createFromBoolean(!$isPure) : TrinaryLogic::createMaybe(),
743754
$throwType,
744755
$asserts,
745756
$acceptsNamedArguments,

stubs/Memcached.stub

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/**
4+
* @phpstan-all-methods-impure
5+
*/
6+
class Memcached {}
7+
8+
/**
9+
* @phpstan-all-methods-impure
10+
*/
11+
class Memcache {}
12+
13+
/**
14+
* @phpstan-all-methods-impure
15+
*/
16+
class MemcachePool {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Rules\Comparison;
4+
5+
use PHPStan\Analyser\RicherScopeGetTypeHelper;
6+
use PHPStan\Rules\Rule;
7+
use PHPStan\Testing\RuleTestCase;
8+
use function array_merge;
9+
10+
/**
11+
* @extends RuleTestCase<StrictComparisonOfDifferentTypesRule>
12+
*/
13+
class Bug14534Test extends RuleTestCase
14+
{
15+
16+
protected function getRule(): Rule
17+
{
18+
return new StrictComparisonOfDifferentTypesRule(
19+
self::getContainer()->getByType(RicherScopeGetTypeHelper::class),
20+
new PossiblyImpureTipHelper(true),
21+
true,
22+
true,
23+
true,
24+
);
25+
}
26+
27+
public function testRule(): void
28+
{
29+
$this->analyse([__DIR__ . '/data/bug-14534.php'], []);
30+
}
31+
32+
public static function getAdditionalConfigFiles(): array
33+
{
34+
return array_merge(
35+
parent::getAdditionalConfigFiles(),
36+
[__DIR__ . '/bug-14534.neon'],
37+
);
38+
}
39+
40+
}

tests/PHPStan/Rules/Comparison/StrictComparisonOfDifferentTypesRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,11 @@ public function testBug14446(): void
11971197
$this->analyse([__DIR__ . '/../../Analyser/data/bug-14446.php'], []);
11981198
}
11991199

1200+
public function testBug13444(): void
1201+
{
1202+
$this->analyse([__DIR__ . '/data/bug-13444.php'], []);
1203+
}
1204+
12001205
public function testBug14473(): void
12011206
{
12021207
$this->analyse([__DIR__ . '/data/bug-14519.php'], []);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
stubFiles:
3+
- data/bug-14534.stub
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Bug13444;
4+
5+
function sayStoreCas(string $key): void
6+
{
7+
$memcached = new \Memcached();
8+
9+
do {
10+
$extendedReturn = $memcached->get($key, null, \Memcached::GET_EXTENDED);
11+
12+
if ($memcached->getResultCode() !== \Memcached::RES_SUCCESS) {
13+
return;
14+
}
15+
16+
if (!is_array($extendedReturn) || !isset($extendedReturn['value']) || !isset($extendedReturn['cas'])) {
17+
return;
18+
}
19+
20+
$data = $extendedReturn['value'];
21+
$cas = $extendedReturn['cas'];
22+
\assert(is_float($cas));
23+
24+
// Do some work on the data..
25+
$memcached->cas($cas, $key, $data);
26+
27+
} while ($memcached->getResultCode() !== \Memcached::RES_SUCCESS);
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Bug14534;
4+
5+
function test(\SplFileObject $spl): bool
6+
{
7+
if ($spl->key() === 1) {
8+
return $spl->key() === 1;
9+
}
10+
11+
return false;
12+
}
13+
14+
function test2(\SplTempFileObject $spl): bool
15+
{
16+
if ($spl->key() === 1) {
17+
return $spl->key() === 1;
18+
}
19+
20+
return false;
21+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?php
2+
3+
/**
4+
* @phpstan-all-methods-impure
5+
*/
6+
class SplFileObject {}

0 commit comments

Comments
 (0)