Skip to content

Commit 58e0cbd

Browse files
committed
Add pureUnlessCallableIsImpureParameters to functionMetadata
1 parent 5f9b9dd commit 58e0cbd

File tree

4 files changed

+47
-9
lines changed

4 files changed

+47
-9
lines changed

bin/generate-function-metadata.php

+28-3
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public function enterNode(Node $node)
7777
$metadata = require __DIR__ . '/functionMetadata_original.php';
7878
foreach ($visitor->functions as $functionName) {
7979
if (array_key_exists($functionName, $metadata)) {
80-
if ($metadata[$functionName]['hasSideEffects']) {
80+
if (isset($metadata[$functionName]['hasSideEffects']) && $metadata[$functionName]['hasSideEffects']) {
8181
if (in_array($functionName, [
8282
'mt_rand',
8383
'rand',
@@ -91,6 +91,14 @@ public function enterNode(Node $node)
9191
}
9292
throw new ShouldNotHappenException($functionName);
9393
}
94+
95+
if (isset($metadata[$functionName]['pureUnlessCallableIsImpureParameters'])) {
96+
$metadata[$functionName] = [
97+
'pureUnlessCallableIsImpureParameters' => $metadata[$functionName]['pureUnlessCallableIsImpureParameters'],
98+
];
99+
100+
continue;
101+
}
94102
}
95103
$metadata[$functionName] = ['hasSideEffects' => false];
96104
}
@@ -128,12 +136,29 @@ public function enterNode(Node $node)
128136
];
129137
php;
130138
$content = '';
139+
$escape = fn (mixed $value): string => var_export($value, true);
140+
$encodeHasSideEffects = fn (array $meta) => [$escape('hasSideEffects'), $escape($meta['hasSideEffects'])];
141+
$encodePureUnlessCallableIsImpureParameters = fn (array $meta) => [
142+
$escape('pureUnlessCallableIsImpureParameters'),
143+
sprintf(
144+
'[%s]',
145+
implode(' ,', array_map(
146+
fn ($key, $param) => sprintf('%s => %s', $escape($key), $escape($param)),
147+
array_keys($meta['pureUnlessCallableIsImpureParameters']),
148+
$meta['pureUnlessCallableIsImpureParameters']),
149+
),
150+
),
151+
];
152+
131153
foreach ($metadata as $name => $meta) {
132154
$content .= sprintf(
133155
"\t%s => [%s => %s],\n",
134156
var_export($name, true),
135-
var_export('hasSideEffects', true),
136-
var_export($meta['hasSideEffects'], true),
157+
...match(true) {
158+
isset($meta['hasSideEffects']) => $encodeHasSideEffects($meta),
159+
isset($meta['pureUnlessCallableIsImpureParameters']) => $encodePureUnlessCallableIsImpureParameters($meta),
160+
default => throw new ShouldNotHappenException($escape($meta)),
161+
},
137162
);
138163
}
139164

src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php

+15-3
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,24 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
8888
$acceptsNamedArguments = $phpDoc->acceptsNamedArguments();
8989
}
9090

91+
$pureUnlessCallableIsImpureParameters = [];
92+
if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) {
93+
$functionMetadata = $this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName);
94+
if (isset($functionMetadata['pureUnlessCallableIsImpureParameters'])) {
95+
$pureUnlessCallableIsImpureParameters = $functionMetadata['pureUnlessCallableIsImpureParameters'];
96+
}
97+
} else {
98+
$functionMetadata = null;
99+
}
100+
91101
$variantsByType = ['positional' => []];
92102
foreach ($functionSignaturesResult as $signatureType => $functionSignatures) {
93103
foreach ($functionSignatures ?? [] as $functionSignature) {
94104
$variantsByType[$signatureType][] = new ExtendedFunctionVariant(
95105
TemplateTypeMap::createEmpty(),
96106
null,
97-
array_map(static function (ParameterSignature $parameterSignature) use ($phpDoc): ExtendedNativeParameterReflection {
107+
array_map(static function (ParameterSignature $parameterSignature, $pureUnlessCallableIsImpureParameters) use ($phpDoc): ExtendedNativeParameterReflection {
108+
$name = $parameterSignature->getName();
98109
$type = $parameterSignature->getType();
99110

100111
$phpDocType = null;
@@ -124,6 +135,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
124135
$phpDoc !== null ? NativeFunctionReflectionProvider::getParamOutTypeFromPhpDoc($parameterSignature->getName(), $phpDoc) : null,
125136
$immediatelyInvokedCallable,
126137
$closureThisType,
138+
isset($pureUnlessCallableIsImpureParameters[$name]) && $pureUnlessCallableIsImpureParameters[$name],
127139
);
128140
}, $functionSignature->getParameters()),
129141
$functionSignature->isVariadic(),
@@ -134,8 +146,8 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
134146
}
135147
}
136148

137-
if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) {
138-
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName)['hasSideEffects']);
149+
if (isset($functionMetadata['hasSideEffects'])) {
150+
$hasSideEffects = TrinaryLogic::createFromBoolean($functionMetadata['hasSideEffects']);
139151
} else {
140152
$hasSideEffects = TrinaryLogic::createMaybe();
141153
}

src/Reflection/SignatureMap/SignatureMapProvider.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ public function hasMethodMetadata(string $className, string $methodName): bool;
2424
public function hasFunctionMetadata(string $name): bool;
2525

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

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

tests/PHPStan/Reflection/SignatureMap/FunctionMetadataTest.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ public function testSchema(): void
1717
$processor = new Processor();
1818
$processor->process(Expect::arrayOf(
1919
Expect::structure([
20-
'hasSideEffects' => Expect::bool()->required(),
20+
'hasSideEffects' => Expect::bool(),
21+
'pureUnlessCallableIsImpureParameters' => Expect::arrayOf(Expect::string(), Expect::bool()),
2122
])->required(),
2223
)->required(), $data);
2324
}

0 commit comments

Comments
 (0)