Skip to content

Commit f1324e0

Browse files
committed
Add pureUnlessCallableIsImpureParameters to functionMetadata
1 parent 873f585 commit f1324e0

File tree

6 files changed

+56
-13
lines changed

6 files changed

+56
-13
lines changed

bin/generate-function-metadata.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,17 @@ public function enterNode(Node $node)
123123
$metadata = require __DIR__ . '/functionMetadata_original.php';
124124
foreach ($visitor->functions as $functionName) {
125125
if (array_key_exists($functionName, $metadata)) {
126-
if ($metadata[$functionName]['hasSideEffects']) {
126+
if (isset($metadata[$functionName]['hasSideEffects']) && $metadata[$functionName]['hasSideEffects']) {
127127
throw new ShouldNotHappenException($functionName);
128128
}
129+
130+
if (isset($metadata[$functionName]['pureUnlessCallableIsImpureParameters'])) {
131+
$metadata[$functionName] = [
132+
'pureUnlessCallableIsImpureParameters' => $metadata[$functionName]['pureUnlessCallableIsImpureParameters'],
133+
];
134+
135+
continue;
136+
}
129137
}
130138
$metadata[$functionName] = ['hasSideEffects' => false];
131139
}
@@ -184,12 +192,32 @@ public function enterNode(Node $node)
184192
];
185193
php;
186194
$content = '';
195+
$escape = static fn (mixed $value): string => var_export($value, true);
196+
$encodeHasSideEffects = static fn (array $meta) => [$escape('hasSideEffects'), $escape($meta['hasSideEffects'])];
197+
$encodePureUnlessCallableIsImpureParameters = static fn (array $meta) => [
198+
$escape('pureUnlessCallableIsImpureParameters'),
199+
sprintf(
200+
'[%s]',
201+
implode(
202+
' ,',
203+
array_map(
204+
static fn ($key, $param) => sprintf('%s => %s', $escape($key), $escape($param)),
205+
array_keys($meta['pureUnlessCallableIsImpureParameters']),
206+
$meta['pureUnlessCallableIsImpureParameters'],
207+
),
208+
),
209+
),
210+
];
211+
187212
foreach ($metadata as $name => $meta) {
188213
$content .= sprintf(
189214
"\t%s => [%s => %s],\n",
190215
var_export($name, true),
191-
var_export('hasSideEffects', true),
192-
var_export($meta['hasSideEffects'], true),
216+
...match (true) {
217+
isset($meta['hasSideEffects']) => $encodeHasSideEffects($meta),
218+
isset($meta['pureUnlessCallableIsImpureParameters']) => $encodePureUnlessCallableIsImpureParameters($meta),
219+
default => throw new ShouldNotHappenException($escape($meta)),
220+
},
193221
);
194222
}
195223

resources/functionMetadata.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1622,4 +1622,4 @@
16221622
'zlib_encode' => ['hasSideEffects' => false],
16231623
'zlib_get_coding_type' => ['hasSideEffects' => false],
16241624

1625-
];
1625+
];

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,7 +678,7 @@ private function createMethod(
678678
}
679679

680680
if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) {
681-
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']);
681+
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects'] ?? false);
682682
} else {
683683
$hasSideEffects = TrinaryLogic::createMaybe();
684684
}

src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,24 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
103103
$acceptsNamedArguments = $phpDoc->acceptsNamedArguments();
104104
}
105105

106+
$pureUnlessCallableIsImpureParameters = [];
107+
if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) {
108+
$functionMetadata = $this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName);
109+
if (isset($functionMetadata['pureUnlessCallableIsImpureParameters'])) {
110+
$pureUnlessCallableIsImpureParameters = $functionMetadata['pureUnlessCallableIsImpureParameters'];
111+
}
112+
} else {
113+
$functionMetadata = null;
114+
}
115+
106116
$variantsByType = ['positional' => []];
107117
foreach ($functionSignaturesResult as $signatureType => $functionSignatures) {
108118
foreach ($functionSignatures ?? [] as $functionSignature) {
109119
$variantsByType[$signatureType][] = new ExtendedFunctionVariant(
110120
TemplateTypeMap::createEmpty(),
111121
null,
112-
array_map(static function (ParameterSignature $parameterSignature) use ($phpDoc): ExtendedNativeParameterReflection {
122+
array_map(static function (ParameterSignature $parameterSignature) use ($phpDoc, $pureUnlessCallableIsImpureParameters): ExtendedNativeParameterReflection {
123+
$name = $parameterSignature->getName();
113124
$type = $parameterSignature->getType();
114125

115126
$phpDocType = null;
@@ -139,7 +150,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
139150
$phpDoc !== null ? NativeFunctionReflectionProvider::getParamOutTypeFromPhpDoc($parameterSignature->getName(), $phpDoc) : null,
140151
$immediatelyInvokedCallable,
141152
$closureThisType,
142-
[],
153+
isset($pureUnlessCallableIsImpureParameters[$name]) && $pureUnlessCallableIsImpureParameters[$name],
143154
);
144155
}, $functionSignature->getParameters()),
145156
$functionSignature->isVariadic(),
@@ -150,8 +161,8 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
150161
}
151162
}
152163

153-
if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) {
154-
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName)['hasSideEffects']);
164+
if (isset($functionMetadata['hasSideEffects'])) {
165+
$hasSideEffects = TrinaryLogic::createFromBoolean($functionMetadata['hasSideEffects']);
155166
} else {
156167
$hasSideEffects = TrinaryLogic::createMaybe();
157168
}

src/Reflection/SignatureMap/SignatureMapProvider.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ public function hasMethodMetadata(string $className, string $methodName): bool;
2626
public function hasFunctionMetadata(string $name): bool;
2727

2828
/**
29-
* @return array{hasSideEffects: bool}
29+
* @return array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>}
3030
*/
3131
public function getMethodMetadata(string $className, string $methodName): array;
3232

3333
/**
34-
* @return array{hasSideEffects: bool}
34+
* @return array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>}
3535
*/
3636
public function getFunctionMetadata(string $functionName): array;
3737

tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Nette\Schema\Expect;
66
use Nette\Schema\Processor;
77
use PHPStan\Testing\PHPStanTestCase;
8+
use function count;
89

910
class FunctionMetadataTest extends PHPStanTestCase
1011
{
@@ -17,8 +18,11 @@ public function testSchema(): void
1718
$processor = new Processor();
1819
$processor->process(Expect::arrayOf(
1920
Expect::structure([
20-
'hasSideEffects' => Expect::bool()->required(),
21-
])->required(),
21+
'hasSideEffects' => Expect::bool(),
22+
'pureUnlessCallableIsImpureParameters' => Expect::arrayOf(Expect::bool(), Expect::string()),
23+
])
24+
->assert(static fn ($v) => count((array) $v) > 0, 'Metadata entries must not be empty.')
25+
->required(),
2226
)->required(), $data);
2327
}
2428

0 commit comments

Comments
 (0)