|
25 | 25 | /** @var string[] */ |
26 | 26 | public array $functions = []; |
27 | 27 |
|
| 28 | + /** @var list<string> */ |
| 29 | + public array $impureFunctions = []; |
| 30 | + |
28 | 31 | /** @var string[] */ |
29 | 32 | public array $methods = []; |
30 | 33 |
|
31 | 34 | public function enterNode(Node $node) |
32 | 35 | { |
33 | 36 | if ($node instanceof Node\Stmt\Function_) { |
| 37 | + assert(isset($node->namespacedName)); |
| 38 | + $functionName = $node->namespacedName->toLowerString(); |
34 | 39 | foreach ($node->attrGroups as $attrGroup) { |
35 | 40 | foreach ($attrGroup->attrs as $attr) { |
36 | 41 | if ($attr->name->toString() === Pure::class) { |
37 | | - $this->functions[] = $node->namespacedName->toLowerString(); |
| 42 | + // PhpStorm stub's #[Pure(true)] mean sthe function has side effects but its return value is important. |
| 43 | + // In PHPStan's criteria, these functions are simply considered as ['hasSideEffect' => true]. |
| 44 | + if (isset($attr->args[0]->value->name->name) && $attr->args[0]->value->name->name === 'true') { |
| 45 | + $this->impureFunctions[] = $functionName; |
| 46 | + } else { |
| 47 | + $this->functions[] = $functionName; |
| 48 | + } |
38 | 49 | break 2; |
39 | 50 | } |
40 | 51 | } |
@@ -74,26 +85,24 @@ public function enterNode(Node $node) |
74 | 85 | ); |
75 | 86 | } |
76 | 87 |
|
| 88 | + /** @var array<string, array{hasSideEffects: bool}> $metadata */ |
77 | 89 | $metadata = require __DIR__ . '/functionMetadata_original.php'; |
78 | 90 | foreach ($visitor->functions as $functionName) { |
79 | 91 | if (array_key_exists($functionName, $metadata)) { |
80 | 92 | if ($metadata[$functionName]['hasSideEffects']) { |
81 | | - if (in_array($functionName, [ |
82 | | - 'mt_rand', |
83 | | - 'rand', |
84 | | - 'random_bytes', |
85 | | - 'random_int', |
86 | | - 'connection_aborted', |
87 | | - 'connection_status', |
88 | | - 'file_get_contents', |
89 | | - ], true)) { |
90 | | - continue; |
91 | | - } |
92 | 93 | throw new ShouldNotHappenException($functionName); |
93 | 94 | } |
94 | 95 | } |
95 | 96 | $metadata[$functionName] = ['hasSideEffects' => false]; |
96 | 97 | } |
| 98 | + foreach ($visitor->impureFunctions as $functionName) { |
| 99 | + if (array_key_exists($functionName, $metadata)) { |
| 100 | + if ($metadata[$functionName]['hasSideEffects']) { |
| 101 | + throw new ShouldNotHappenException($functionName); |
| 102 | + } |
| 103 | + } |
| 104 | + $metadata[$functionName] = ['hasSideEffects' => true]; |
| 105 | + } |
97 | 106 |
|
98 | 107 | foreach ($visitor->methods as $methodName) { |
99 | 108 | if (array_key_exists($methodName, $metadata)) { |
|
0 commit comments