Skip to content

Commit d7579c4

Browse files
committed
Pure cont'd
1 parent 3ef4883 commit d7579c4

File tree

4 files changed

+157
-0
lines changed

4 files changed

+157
-0
lines changed

src/Analyser/NodeScopeResolver.php

+29
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,14 @@ private function processStmtNode(
648648
return;
649649
}
650650
if ($node instanceof PropertyAssignNode) {
651+
if (
652+
$node->getPropertyFetch() instanceof Expr\PropertyFetch
653+
&& $scope->getFunction() instanceof PhpMethodFromParserNodeReflection
654+
&& $scope->getFunction()->getDeclaringClass()->hasConstructor()
655+
&& $scope->getFunction()->getDeclaringClass()->getConstructor()->getName() === $scope->getFunction()->getName()
656+
) {
657+
return;
658+
}
651659
$methodImpurePoints[] = new ImpurePoint(
652660
$scope,
653661
$node,
@@ -2934,6 +2942,27 @@ static function (): void {
29342942
$impurePoints[] = new ImpurePoint($scope, $expr, 'print', 'print', true);
29352943
$hasYield = $result->hasYield();
29362944

2945+
$scope = $result->getScope();
2946+
} elseif ($expr instanceof Cast\String_) {
2947+
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context->enterDeep());
2948+
$throwPoints = $result->getThrowPoints();
2949+
$impurePoints = $result->getImpurePoints();
2950+
$hasYield = $result->hasYield();
2951+
2952+
$exprType = $scope->getType($expr->expr);
2953+
$toStringMethod = $scope->getMethodReflection($exprType, '__toString');
2954+
if ($toStringMethod !== null) {
2955+
if (!$toStringMethod->hasSideEffects()->no()) {
2956+
$impurePoints[] = new ImpurePoint(
2957+
$scope,
2958+
$expr,
2959+
'methodCall',
2960+
sprintf('call to method %s::%s()', $toStringMethod->getDeclaringClass()->getDisplayName(), $toStringMethod->getName()),
2961+
$toStringMethod->isPure()->no(),
2962+
);
2963+
}
2964+
}
2965+
29372966
$scope = $result->getScope();
29382967
} elseif (
29392968
$expr instanceof Expr\BitwiseNot

tests/PHPStan/Rules/Pure/PureMethodRuleTest.php

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use PHPStan\Rules\Rule;
66
use PHPStan\Testing\RuleTestCase;
7+
use const PHP_VERSION_ID;
78

89
/**
910
* @extends RuleTestCase<PureMethodRule>
@@ -115,6 +116,32 @@ public function testRule(): void
115116
'Impure assign to superglobal variable in pure method PureMethod\ClassWithVoidMethods::purePostGetAssign().',
116117
231,
117118
],
119+
[
120+
'Possibly impure call to method PureMethod\MaybePureMagicMethods::__toString() in pure method PureMethod\TestMagicMethods::doFoo().',
121+
295,
122+
],
123+
[
124+
'Impure call to method PureMethod\ImpureMagicMethods::__toString() in pure method PureMethod\TestMagicMethods::doFoo().',
125+
296,
126+
],
127+
]);
128+
}
129+
130+
public function testPureConstructor(): void
131+
{
132+
if (PHP_VERSION_ID < 80000) {
133+
$this->markTestSkipped('Test requires PHP 8.0.');
134+
}
135+
136+
$this->analyse([__DIR__ . '/data/pure-constructor.php'], [
137+
[
138+
'Impure property assignment in pure method PureConstructor\Foo::__construct().',
139+
19,
140+
],
141+
[
142+
'Method PureConstructor\Bar::__construct() is marked as impure but does not have any side effects.',
143+
30,
144+
],
118145
]);
119146
}
120147

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php // lint >= 8.0
2+
3+
namespace PureConstructor;
4+
5+
class Foo
6+
{
7+
8+
private string $prop;
9+
10+
public static $staticProp = 1;
11+
12+
/** @phpstan-pure */
13+
public function __construct(
14+
public int $test,
15+
string $prop,
16+
)
17+
{
18+
$this->prop = $prop;
19+
self::$staticProp++;
20+
}
21+
22+
}
23+
24+
class Bar
25+
{
26+
27+
private string $prop;
28+
29+
/** @phpstan-impure */
30+
public function __construct(
31+
public int $test,
32+
string $prop,
33+
)
34+
{
35+
$this->prop = $prop;
36+
}
37+
38+
}

tests/PHPStan/Rules/Pure/data/pure-method.php

+63
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,66 @@ public function purePostGetAssign(array $post = [], array $get = []): int
234234
}
235235

236236
}
237+
238+
class NoMagicMethods
239+
{
240+
241+
}
242+
243+
class PureMagicMethods
244+
{
245+
246+
/**
247+
* @phpstan-pure
248+
*/
249+
public function __toString(): string
250+
{
251+
return 'one';
252+
}
253+
254+
}
255+
256+
class MaybePureMagicMethods
257+
{
258+
259+
public function __toString(): string
260+
{
261+
return 'one';
262+
}
263+
264+
}
265+
266+
class ImpureMagicMethods
267+
{
268+
269+
/**
270+
* @phpstan-impure
271+
*/
272+
public function __toString(): string
273+
{
274+
sleep(1);
275+
return 'one';
276+
}
277+
278+
}
279+
280+
class TestMagicMethods
281+
{
282+
283+
/**
284+
* @phpstan-pure
285+
*/
286+
public function doFoo(
287+
NoMagicMethods $no,
288+
PureMagicMethods $pure,
289+
MaybePureMagicMethods $maybe,
290+
ImpureMagicMethods $impure
291+
)
292+
{
293+
(string) $no;
294+
(string) $pure;
295+
(string) $maybe;
296+
(string) $impure;
297+
}
298+
299+
}

0 commit comments

Comments
 (0)