Skip to content

Commit 0019dcf

Browse files
committed
Add pureUnlessCallableIsImpureParameters to functionMetadata
1 parent 081f883 commit 0019dcf

File tree

5 files changed

+55
-11
lines changed

5 files changed

+55
-11
lines changed

bin/generate-function-metadata.php

Lines changed: 31 additions & 3 deletions
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,32 @@ public function enterNode(Node $node)
128136
];
129137
php;
130138
$content = '';
139+
$escape = static fn (mixed $value): string => var_export($value, true);
140+
$encodeHasSideEffects = static fn (array $meta) => [$escape('hasSideEffects'), $escape($meta['hasSideEffects'])];
141+
$encodePureUnlessCallableIsImpureParameters = static fn (array $meta) => [
142+
$escape('pureUnlessCallableIsImpureParameters'),
143+
sprintf(
144+
'[%s]',
145+
implode(
146+
' ,',
147+
array_map(
148+
fn ($key, $param) => sprintf('%s => %s', $escape($key), $escape($param)),
149+
array_keys($meta['pureUnlessCallableIsImpureParameters']),
150+
$meta['pureUnlessCallableIsImpureParameters']
151+
),
152+
),
153+
),
154+
];
155+
131156
foreach ($metadata as $name => $meta) {
132157
$content .= sprintf(
133158
"\t%s => [%s => %s],\n",
134159
var_export($name, true),
135-
var_export('hasSideEffects', true),
136-
var_export($meta['hasSideEffects'], true),
160+
...match(true) {
161+
isset($meta['hasSideEffects']) => $encodeHasSideEffects($meta),
162+
isset($meta['pureUnlessCallableIsImpureParameters']) => $encodePureUnlessCallableIsImpureParameters($meta),
163+
default => throw new ShouldNotHappenException($escape($meta)),
164+
},
137165
);
138166
}
139167

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ private function createMethod(
608608
}
609609

610610
if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) {
611-
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']);
611+
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects'] ?? false);
612612
} else {
613613
$hasSideEffects = TrinaryLogic::createMaybe();
614614
}

src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php

Lines changed: 15 additions & 3 deletions
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) use ($phpDoc, $pureUnlessCallableIsImpureParameters): 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

Lines changed: 2 additions & 2 deletions
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

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)