|
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