Skip to content

Commit d4edc59

Browse files
committed
Do not remember values of function with side effects
1 parent edc8446 commit d4edc59

File tree

6 files changed

+116
-15
lines changed

6 files changed

+116
-15
lines changed

src/Analyser/TypeSpecifier.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -869,13 +869,40 @@ public function create(
869869
Expr $expr,
870870
Type $type,
871871
TypeSpecifierContext $context,
872-
bool $overwrite = false
872+
bool $overwrite = false,
873+
?Scope $scope = null
873874
): SpecifiedTypes
874875
{
875876
if ($expr instanceof New_ || $expr instanceof Instanceof_) {
876877
return new SpecifiedTypes();
877878
}
878879

880+
if (
881+
$expr instanceof FuncCall
882+
&& $expr->name instanceof Name
883+
&& $this->reflectionProvider->hasFunction($expr->name, $scope)
884+
) {
885+
$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);
886+
if ($functionReflection->hasSideEffects()->yes()) {
887+
return new SpecifiedTypes();
888+
}
889+
}
890+
891+
if (
892+
$expr instanceof MethodCall
893+
&& $expr->name instanceof Node\Identifier
894+
&& $scope !== null
895+
) {
896+
$methodName = $expr->name->toString();
897+
$calledOnType = $scope->getType($expr->var);
898+
if ($calledOnType->hasMethod($methodName)->yes()) {
899+
$methodReflection = $calledOnType->getMethod($methodName, $scope);
900+
if ($methodReflection->hasSideEffects()->yes()) {
901+
return new SpecifiedTypes();
902+
}
903+
}
904+
}
905+
879906
$sureTypes = [];
880907
$sureNotTypes = [];
881908

src/Rules/Comparison/ImpossibleCheckTypeHelper.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ public function findSpecifiedType(
179179
return null;
180180
}
181181

182+
if ($sureType[0] === $node) {
183+
return null;
184+
}
185+
182186
if ($this->treatPhpDocTypesAsCertain) {
183187
$argumentType = $scope->getType($sureType[0]);
184188
} else {
@@ -202,6 +206,10 @@ public function findSpecifiedType(
202206
return null;
203207
}
204208

209+
if ($sureNotType[0] === $node) {
210+
return null;
211+
}
212+
205213
if ($this->treatPhpDocTypesAsCertain) {
206214
$argumentType = $scope->getType($sureNotType[0]);
207215
} else {

tests/PHPStan/Analyser/NodeScopeResolverTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5760,6 +5760,11 @@ public function dataBug560(): array
57605760
return $this->gatherAssertTypes(__DIR__ . '/data/bug-560.php');
57615761
}
57625762

5763+
public function dataDoNotRememberImpureFunctions(): array
5764+
{
5765+
return $this->gatherAssertTypes(__DIR__ . '/data/do-not-remember-impure-functions.php');
5766+
}
5767+
57635768
/**
57645769
* @dataProvider dataArrayFunctions
57655770
* @param string $description
@@ -11395,6 +11400,7 @@ private function gatherAssertTypes(string $file): array
1139511400
* @dataProvider dataBug3190
1139611401
* @dataProvider dataTernarySpecifiedTypes
1139711402
* @dataProvider dataBug560
11403+
* @dataProvider dataDoNotRememberImpureFunctions
1139811404
* @param string $assertType
1139911405
* @param string $file
1140011406
* @param mixed ...$args
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace DoNotRememberImpureFunctions;
4+
5+
use function PHPStan\Analyser\assertType;
6+
7+
class Foo
8+
{
9+
10+
public function doFoo()
11+
{
12+
function (): void {
13+
if (rand(0, 1)) {
14+
assertType('int', rand(0, 1));
15+
}
16+
};
17+
18+
function (): void {
19+
if (rand(0, 1) === 0) {
20+
assertType('int', rand(0, 1));
21+
}
22+
};
23+
}
24+
25+
public function doBar(): bool
26+
{
27+
28+
}
29+
30+
/** @phpstan-pure */
31+
public function doBaz(): bool
32+
{
33+
34+
}
35+
36+
/** @phpstan-impure */
37+
public function doLorem(): bool
38+
{
39+
40+
}
41+
42+
public function doIpsum()
43+
{
44+
if ($this->doBar() === true) {
45+
assertType('true', $this->doBar());
46+
}
47+
48+
if ($this->doBaz() === true) {
49+
assertType('true', $this->doBaz());
50+
}
51+
52+
if ($this->doLorem() === true) {
53+
assertType('bool', $this->doLorem());
54+
}
55+
}
56+
57+
public function doDolor()
58+
{
59+
if ($this->doBar()) {
60+
assertType('true', $this->doBar());
61+
}
62+
63+
if ($this->doBaz()) {
64+
assertType('true', $this->doBaz());
65+
}
66+
67+
if ($this->doLorem()) {
68+
assertType('bool', $this->doLorem());
69+
}
70+
}
71+
72+
}

tests/PHPStan/Analyser/data/isset.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ public function doFoo(array $integers, string $string, $mixedIsset, $mixedArrayK
1515
'a' => 1,
1616
'b' => 2,
1717
];
18-
} elseif (rand(0, 11) === 0) {
18+
} elseif (rand(0, 10) === 0) {
1919
$array = [
2020
'a' => 2,
2121
];
22-
} elseif (rand(0, 12) === 0) {
22+
} elseif (rand(0, 10) === 0) {
2323
$array = [
2424
'a' => 3,
2525
'b' => 3,

tests/PHPStan/Rules/Methods/ReturnTypeRuleTest.php

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,6 @@ public function testReturnTypeRule(): void
183183
'Method ReturnTypes\TrickyVoid::returnVoidOrInt() should return int|void but returns string.',
184184
656,
185185
],
186-
[
187-
'Method ReturnTypes\TernaryWithJsonEncode::toJsonOrNull() should return string|null but returns string|false|null.',
188-
671,
189-
],
190-
[
191-
'Method ReturnTypes\TernaryWithJsonEncode::toJson() should return string but returns string|false.',
192-
684,
193-
],
194186
[
195187
'Method ReturnTypes\TernaryWithJsonEncode::toJson() should return string but returns string|false.',
196188
687,
@@ -235,10 +227,6 @@ public function testReturnTypeRule(): void
235227
'Method ReturnTypes\WrongMagicMethods::__clone() with return type void returns int but should not return anything.',
236228
757,
237229
],
238-
[
239-
'Method ReturnTypes\ReturnSpecifiedMethodCall::doFoo() should return string but returns string|false.',
240-
776,
241-
],
242230
[
243231
'Method ReturnTypes\ArrayFillKeysIssue::getIPs2() should return array<string, array<ReturnTypes\Foo>> but returns array<string, array<int, ReturnTypes\Bar>>.',
244232
815,

0 commit comments

Comments
 (0)