Skip to content

Commit

Permalink
Add pureUnlessCallableIsImpureParameters to functionMetadata
Browse files Browse the repository at this point in the history
  • Loading branch information
zonuexe committed Nov 19, 2024
1 parent 081f883 commit 0019dcf
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 11 deletions.
34 changes: 31 additions & 3 deletions bin/generate-function-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function enterNode(Node $node)
$metadata = require __DIR__ . '/functionMetadata_original.php';
foreach ($visitor->functions as $functionName) {
if (array_key_exists($functionName, $metadata)) {
if ($metadata[$functionName]['hasSideEffects']) {
if (isset($metadata[$functionName]['hasSideEffects']) && $metadata[$functionName]['hasSideEffects']) {
if (in_array($functionName, [
'mt_rand',
'rand',
Expand All @@ -91,6 +91,14 @@ public function enterNode(Node $node)
}
throw new ShouldNotHappenException($functionName);
}

if (isset($metadata[$functionName]['pureUnlessCallableIsImpureParameters'])) {
$metadata[$functionName] = [
'pureUnlessCallableIsImpureParameters' => $metadata[$functionName]['pureUnlessCallableIsImpureParameters'],
];

continue;
}
}
$metadata[$functionName] = ['hasSideEffects' => false];
}
Expand Down Expand Up @@ -128,12 +136,32 @@ public function enterNode(Node $node)
];
php;
$content = '';
$escape = static fn (mixed $value): string => var_export($value, true);
$encodeHasSideEffects = static fn (array $meta) => [$escape('hasSideEffects'), $escape($meta['hasSideEffects'])];
$encodePureUnlessCallableIsImpureParameters = static fn (array $meta) => [
$escape('pureUnlessCallableIsImpureParameters'),
sprintf(
'[%s]',
implode(
' ,',
array_map(
fn ($key, $param) => sprintf('%s => %s', $escape($key), $escape($param)),
array_keys($meta['pureUnlessCallableIsImpureParameters']),
$meta['pureUnlessCallableIsImpureParameters']
),
),
),
];

foreach ($metadata as $name => $meta) {
$content .= sprintf(
"\t%s => [%s => %s],\n",
var_export($name, true),
var_export('hasSideEffects', true),
var_export($meta['hasSideEffects'], true),
...match(true) {
isset($meta['hasSideEffects']) => $encodeHasSideEffects($meta),
isset($meta['pureUnlessCallableIsImpureParameters']) => $encodePureUnlessCallableIsImpureParameters($meta),
default => throw new ShouldNotHappenException($escape($meta)),
},
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Reflection/Php/PhpClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ private function createMethod(
}

if ($this->signatureMapProvider->hasMethodMetadata($declaringClassName, $methodReflection->getName())) {
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects']);
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getMethodMetadata($declaringClassName, $methodReflection->getName())['hasSideEffects'] ?? false);
} else {
$hasSideEffects = TrinaryLogic::createMaybe();
}
Expand Down
18 changes: 15 additions & 3 deletions src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,24 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
$acceptsNamedArguments = $phpDoc->acceptsNamedArguments();
}

$pureUnlessCallableIsImpureParameters = [];
if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) {
$functionMetadata = $this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName);
if (isset($functionMetadata['pureUnlessCallableIsImpureParameters'])) {
$pureUnlessCallableIsImpureParameters = $functionMetadata['pureUnlessCallableIsImpureParameters'];
}
} else {
$functionMetadata = null;
}

$variantsByType = ['positional' => []];
foreach ($functionSignaturesResult as $signatureType => $functionSignatures) {
foreach ($functionSignatures ?? [] as $functionSignature) {
$variantsByType[$signatureType][] = new ExtendedFunctionVariant(
TemplateTypeMap::createEmpty(),
null,
array_map(static function (ParameterSignature $parameterSignature) use ($phpDoc): ExtendedNativeParameterReflection {
array_map(static function (ParameterSignature $parameterSignature) use ($phpDoc, $pureUnlessCallableIsImpureParameters): ExtendedNativeParameterReflection {
$name = $parameterSignature->getName();
$type = $parameterSignature->getType();

$phpDocType = null;
Expand Down Expand Up @@ -124,6 +135,7 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
$phpDoc !== null ? NativeFunctionReflectionProvider::getParamOutTypeFromPhpDoc($parameterSignature->getName(), $phpDoc) : null,
$immediatelyInvokedCallable,
$closureThisType,
isset($pureUnlessCallableIsImpureParameters[$name]) && $pureUnlessCallableIsImpureParameters[$name],
);
}, $functionSignature->getParameters()),
$functionSignature->isVariadic(),
Expand All @@ -134,8 +146,8 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
}
}

if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) {
$hasSideEffects = TrinaryLogic::createFromBoolean($this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName)['hasSideEffects']);
if (isset($functionMetadata['hasSideEffects'])) {
$hasSideEffects = TrinaryLogic::createFromBoolean($functionMetadata['hasSideEffects']);
} else {
$hasSideEffects = TrinaryLogic::createMaybe();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Reflection/SignatureMap/SignatureMapProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ public function hasMethodMetadata(string $className, string $methodName): bool;
public function hasFunctionMetadata(string $name): bool;

/**
* @return array{hasSideEffects: bool}
* @return array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>}
*/
public function getMethodMetadata(string $className, string $methodName): array;

/**
* @return array{hasSideEffects: bool}
* @return array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>}
*/
public function getFunctionMetadata(string $functionName): array;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Nette\Schema\Expect;
use Nette\Schema\Processor;
use PHPStan\Testing\PHPStanTestCase;
use function count;

class FunctionMetadataTest extends PHPStanTestCase
{
Expand All @@ -17,8 +18,11 @@ public function testSchema(): void
$processor = new Processor();
$processor->process(Expect::arrayOf(
Expect::structure([
'hasSideEffects' => Expect::bool()->required(),
])->required(),
'hasSideEffects' => Expect::bool(),
'pureUnlessCallableIsImpureParameters' => Expect::arrayOf(Expect::bool(), Expect::string()),
])
->assert(static fn ($v) => count((array)$v) > 0, 'Metadata entries must not be empty.')
->required(),
)->required(), $data);
}

Expand Down

0 comments on commit 0019dcf

Please sign in to comment.