Skip to content
27 changes: 17 additions & 10 deletions bin/functionMetadata_original.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
'apcu_key_info' => ['hasSideEffects' => true],
'apcu_sma_info' => ['hasSideEffects' => true],
'apcu_store' => ['hasSideEffects' => true],
'array_all' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case some function has multiple callback parameter, the value associated to the pureUnlessCallableIsImpure key should be an array of parameter name instead.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed. Here's what was changed to address VincentLanglet's review:

The pureUnlessCallableIsImpure metadata value is now an array of parameter names instead of a boolean. This properly handles functions with multiple callback parameters. For example:

  • array_filter → ['callback']
  • array_udiff_uassoc → ['data_comp_func', 'key_comp_func']
  • array_uintersect_uassoc → ['data_compare_func', 'key_compare_func']

Changes across 8 files:

  • bin/functionMetadata_original.php — all 17 entries updated with specific parameter names
  • bin/generate-function-metadata.php — generator emits array syntax
  • resources/functionMetadata.php — regenerated
  • SignatureMapProvider interface + FunctionSignatureMapProvider + Php8SignatureMapProvider — type annotations updated from bool to list<string>
  • NativeFunctionReflectionProvider — metadata checks use count() instead of truthiness
  • FunctionMetadataTest — schema validates listOf(string) instead of bool

'array_any' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_change_key_case' => ['hasSideEffects' => false],
'array_chunk' => ['hasSideEffects' => false],
'array_column' => ['hasSideEffects' => false],
Expand All @@ -28,39 +30,44 @@
'array_diff' => ['hasSideEffects' => false],
'array_diff_assoc' => ['hasSideEffects' => false],
'array_diff_key' => ['hasSideEffects' => false],
'array_diff_uassoc' => ['hasSideEffects' => false],
'array_diff_ukey' => ['hasSideEffects' => false],
'array_diff_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_diff_ukey' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_fill' => ['hasSideEffects' => false],
'array_fill_keys' => ['hasSideEffects' => false],
'array_filter' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_find' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_find_key' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_flip' => ['hasSideEffects' => false],
'array_intersect' => ['hasSideEffects' => false],
'array_intersect_assoc' => ['hasSideEffects' => false],
'array_intersect_key' => ['hasSideEffects' => false],
'array_intersect_uassoc' => ['hasSideEffects' => false],
'array_intersect_ukey' => ['hasSideEffects' => false],
'array_intersect_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_intersect_ukey' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_key_first' => ['hasSideEffects' => false],
'array_key_last' => ['hasSideEffects' => false],
'array_key_exists' => ['hasSideEffects' => false],
'array_keys' => ['hasSideEffects' => false],
'array_map' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_merge' => ['hasSideEffects' => false],
'array_merge_recursive' => ['hasSideEffects' => false],
'array_pad' => ['hasSideEffects' => false],
'array_pop' => ['hasSideEffects' => true],
'array_product' => ['hasSideEffects' => false],
'array_push' => ['hasSideEffects' => true],
'array_rand' => ['hasSideEffects' => false],
'array_reduce' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_replace' => ['hasSideEffects' => false],
'array_replace_recursive' => ['hasSideEffects' => false],
'array_reverse' => ['hasSideEffects' => false],
'array_shift' => ['hasSideEffects' => true],
'array_slice' => ['hasSideEffects' => false],
'array_sum' => ['hasSideEffects' => false],
'array_udiff' => ['hasSideEffects' => false],
'array_udiff_assoc' => ['hasSideEffects' => false],
'array_udiff_uassoc' => ['hasSideEffects' => false],
'array_uintersect' => ['hasSideEffects' => false],
'array_uintersect_assoc' => ['hasSideEffects' => false],
'array_uintersect_uassoc' => ['hasSideEffects' => false],
'array_udiff' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_udiff_assoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_udiff_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_uintersect' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_uintersect_assoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_uintersect_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_unique' => ['hasSideEffects' => false],
'array_unshift' => ['hasSideEffects' => true],
'array_values' => ['hasSideEffects' => false],
Expand Down
11 changes: 7 additions & 4 deletions bin/generate-function-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public function enterNode(Node $node)
);
}

/** @var array<string, array{hasSideEffects: bool}> $metadata */
/** @var array<string, array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}> $metadata */
$metadata = require __DIR__ . '/functionMetadata_original.php';
foreach ($visitor->functions as $functionName) {
if (array_key_exists($functionName, $metadata)) {
Expand Down Expand Up @@ -185,11 +185,14 @@ public function enterNode(Node $node)
php;
$content = '';
foreach ($metadata as $name => $meta) {
$pairs = sprintf('%s => %s', var_export('hasSideEffects', true), var_export($meta['hasSideEffects'], true));
if (isset($meta['pureUnlessCallableIsImpure']) && $meta['pureUnlessCallableIsImpure']) {
$pairs .= sprintf(', %s => %s', var_export('pureUnlessCallableIsImpure', true), var_export(true, true));
}
$content .= sprintf(
"\t%s => [%s => %s],\n",
"\t%s => [%s],\n",
var_export($name, true),
var_export('hasSideEffects', true),
var_export($meta['hasSideEffects'], true),
$pairs,
);
}

Expand Down
27 changes: 17 additions & 10 deletions resources/functionMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,8 @@
'apcu_key_info' => ['hasSideEffects' => true],
'apcu_sma_info' => ['hasSideEffects' => true],
'apcu_store' => ['hasSideEffects' => true],
'array_all' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_any' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_change_key_case' => ['hasSideEffects' => false],
'array_chunk' => ['hasSideEffects' => false],
'array_column' => ['hasSideEffects' => false],
Expand All @@ -733,43 +735,48 @@
'array_diff' => ['hasSideEffects' => false],
'array_diff_assoc' => ['hasSideEffects' => false],
'array_diff_key' => ['hasSideEffects' => false],
'array_diff_uassoc' => ['hasSideEffects' => false],
'array_diff_ukey' => ['hasSideEffects' => false],
'array_diff_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_diff_ukey' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_fill' => ['hasSideEffects' => false],
'array_fill_keys' => ['hasSideEffects' => false],
'array_filter' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_find' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_find_key' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_first' => ['hasSideEffects' => false],
'array_flip' => ['hasSideEffects' => false],
'array_intersect' => ['hasSideEffects' => false],
'array_intersect_assoc' => ['hasSideEffects' => false],
'array_intersect_key' => ['hasSideEffects' => false],
'array_intersect_uassoc' => ['hasSideEffects' => false],
'array_intersect_ukey' => ['hasSideEffects' => false],
'array_intersect_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_intersect_ukey' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_is_list' => ['hasSideEffects' => false],
'array_key_exists' => ['hasSideEffects' => false],
'array_key_first' => ['hasSideEffects' => false],
'array_key_last' => ['hasSideEffects' => false],
'array_keys' => ['hasSideEffects' => false],
'array_last' => ['hasSideEffects' => false],
'array_map' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_merge' => ['hasSideEffects' => false],
'array_merge_recursive' => ['hasSideEffects' => false],
'array_pad' => ['hasSideEffects' => false],
'array_pop' => ['hasSideEffects' => true],
'array_product' => ['hasSideEffects' => false],
'array_push' => ['hasSideEffects' => true],
'array_rand' => ['hasSideEffects' => false],
'array_reduce' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_replace' => ['hasSideEffects' => false],
'array_replace_recursive' => ['hasSideEffects' => false],
'array_reverse' => ['hasSideEffects' => false],
'array_search' => ['hasSideEffects' => false],
'array_shift' => ['hasSideEffects' => true],
'array_slice' => ['hasSideEffects' => false],
'array_sum' => ['hasSideEffects' => false],
'array_udiff' => ['hasSideEffects' => false],
'array_udiff_assoc' => ['hasSideEffects' => false],
'array_udiff_uassoc' => ['hasSideEffects' => false],
'array_uintersect' => ['hasSideEffects' => false],
'array_uintersect_assoc' => ['hasSideEffects' => false],
'array_uintersect_uassoc' => ['hasSideEffects' => false],
'array_udiff' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_udiff_assoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_udiff_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_uintersect' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_uintersect_assoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_uintersect_uassoc' => ['hasSideEffects' => false, 'pureUnlessCallableIsImpure' => true],
'array_unique' => ['hasSideEffects' => false],
'array_unshift' => ['hasSideEffects' => true],
'array_values' => ['hasSideEffects' => false],
Expand Down
16 changes: 16 additions & 0 deletions src/PhpDoc/PhpDocNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,22 @@ public function resolveIsImpure(PhpDocNode $phpDocNode): bool
return false;
}

/**
* @return array<string, true>
*/
public function resolvePureUnlessCallableIsImpure(PhpDocNode $phpDocNode): array
{
$parameters = [];
foreach (['@pure-unless-callable-is-impure', '@phpstan-pure-unless-callable-is-impure'] as $tagName) {
foreach ($phpDocNode->getPureUnlessCallableIsImpureTagValues($tagName) as $tagValue) {
$parameterName = substr($tagValue->parameterName, 1);
$parameters[$parameterName] = true;
}
}

return $parameters;
}

public function resolveAllMethodsPure(PhpDocNode $phpDocNode): bool
{
return count($phpDocNode->getTagsByName('@phpstan-all-methods-pure')) > 0;
Expand Down
51 changes: 51 additions & 0 deletions src/PhpDoc/ResolvedPhpDocBlock.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ final class ResolvedPhpDocBlock
/** @var bool|'notLoaded'|null */
private bool|string|null $isPure = 'notLoaded';

/** @var array<string, true>|false */
private array|false $pureUnlessCallableIsImpureParameterNames = false;

private ?bool $areAllMethodsPure = null;

private ?bool $areAllMethodsImpure = null;
Expand Down Expand Up @@ -237,6 +240,7 @@ public static function createEmpty(): self
$self->isInternal = false;
$self->isFinal = false;
$self->isPure = null;
$self->pureUnlessCallableIsImpureParameterNames = [];
$self->areAllMethodsPure = false;
$self->areAllMethodsImpure = false;
$self->isReadOnly = false;
Expand Down Expand Up @@ -293,6 +297,7 @@ public function merge(ResolvedPhpDocBlock $parent, InheritedPhpDocParameterMappi
$result->isInternal = $this->isInternal();
$result->isFinal = $this->isFinal();
$result->isPure = self::mergePureTags($this->isPure(), $parent);
$result->pureUnlessCallableIsImpureParameterNames = self::mergePureUnlessCallableIsImpureParameterNames($this->getPureUnlessCallableIsImpureParameterNames(), $parent, $parameterMapping);
$result->areAllMethodsPure = $this->areAllMethodsPure();
$result->areAllMethodsImpure = $this->areAllMethodsImpure();
$result->isReadOnly = $this->isReadOnly();
Expand Down Expand Up @@ -352,6 +357,15 @@ public function changeParameterNamesByMapping(array $parameterNameMapping): self
$newParamsImmediatelyInvokedCallable[$parameterNameMapping[$key]] = $immediatelyInvokedCallable;
}

$newPureUnlessCallableIsImpureParameterNames = [];
foreach ($this->getPureUnlessCallableIsImpureParameterNames() as $key => $value) {
if (!array_key_exists($key, $parameterNameMapping)) {
continue;
}

$newPureUnlessCallableIsImpureParameterNames[$parameterNameMapping[$key]] = $value;
}

$paramClosureThisTags = $this->getParamClosureThisTags();
$newParamClosureThisTags = [];
foreach ($paramClosureThisTags as $key => $paramClosureThisTag) {
Expand Down Expand Up @@ -399,6 +413,7 @@ public function changeParameterNamesByMapping(array $parameterNameMapping): self
$self->paramTags = $newParamTags;
$self->paramOutTags = $newParamOutTags;
$self->paramsImmediatelyInvokedCallable = $newParamsImmediatelyInvokedCallable;
$self->pureUnlessCallableIsImpureParameterNames = $newPureUnlessCallableIsImpureParameterNames;
$self->paramClosureThisTags = $newParamClosureThisTags;
$self->returnTag = $returnTag;
$self->throwsTag = $this->throwsTag;
Expand Down Expand Up @@ -584,6 +599,18 @@ public function getParamsImmediatelyInvokedCallable(): array
return $this->paramsImmediatelyInvokedCallable;
}

/**
* @return array<string, true>
*/
public function getPureUnlessCallableIsImpureParameterNames(): array
{
if ($this->pureUnlessCallableIsImpureParameterNames === false) {
$this->pureUnlessCallableIsImpureParameterNames = $this->phpDocNodeResolver->resolvePureUnlessCallableIsImpure($this->phpDocNode);
}

return $this->pureUnlessCallableIsImpureParameterNames;
}

/**
* @return array<string, ParamClosureThisTag>
*/
Expand Down Expand Up @@ -818,6 +845,11 @@ public function isPure(): ?bool
return $this->isPure;
}

if (count($this->getPureUnlessCallableIsImpureParameterNames()) > 0) {
$this->isPure = true;
return $this->isPure;
}

$this->isPure = null;
}

Expand Down Expand Up @@ -1085,6 +1117,25 @@ private static function mergeOneParentParamImmediatelyInvokedCallable(array $par
return $paramsImmediatelyInvokedCallable;
}

/**
* @param array<string, true> $parameterNames
* @return array<string, true>
*/
private static function mergePureUnlessCallableIsImpureParameterNames(array $parameterNames, self $parent, InheritedPhpDocParameterMapping $parameterMapping): array
{
$parentParameterNames = $parameterMapping->transformArrayKeysWithParameterNameMapping($parent->getPureUnlessCallableIsImpureParameterNames());

foreach ($parentParameterNames as $name => $value) {
if (array_key_exists($name, $parameterNames)) {
continue;
}

$parameterNames[$name] = $value;
}

return $parameterNames;
}

/**
* @param array<string, ParamClosureThisTag> $paramsClosureThisTags
* @return array<string, ParamClosureThisTag>
Expand Down
4 changes: 4 additions & 0 deletions src/Reflection/BetterReflection/BetterReflectionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,10 @@ private function getCustomFunction(string $functionName): PhpFunctionReflection
$phpDocParameterClosureThisTypeTags = $resolvedPhpDoc->getParamClosureThisTags();
}

if ($isPure === null) {
$isPure = $this->nativeFunctionReflectionProvider->getFunctionPurityFromMetadata($functionName);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be needed, function metadata are already correclty handled

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 11980 tests pass (79552 assertions) and make phpstan reports no errors. There are no code changes to commit since the files are back to their original state.

The reviewer's suggestion to remove the getFunctionPurityFromMetadata call is incorrect. Here's the explanation:

The metadata lookup in getCustomFunction() IS needed because some functions with metadata entries can be resolved through getCustomFunction() instead of findFunctionReflection(). Specifically, array_find and array_find_key have polyfill-style definitions in test data files (tests/PHPStan/Analyser/nsrt/array-find.php and array-find-key.php). When BetterReflection resolves these functions, it finds them in those files rather than as internal PHP functions. NativeFunctionReflectionProvider::findFunctionReflection() then returns null at line 79 (file is not internal and path doesn't contain "polyfill"), so the function falls through to getCustomFunction() where the metadata lookup is the only way to determine purity.

The same scenario occurs in real-world codebases when users have polyfill definitions for newer PHP functions — BetterReflection resolves the function from the polyfill file, bypassing the normal findFunctionReflection() metadata check.

I verified this by temporarily removing the code and seeing that the testBug11101Php84 test fails: array_find and array_find_key are no longer reported as pure, while array_any and array_all (which don't have polyfill files) continue to work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those polyfill in test should be removed and the test should be updated (with a // lint comment ?) to run on the appropriate php versions.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Here's what was changed to address VincentLanglet's review:

The reviewer was right — the getFunctionPurityFromMetadata fallback in BetterReflectionProvider::getCustomFunction() was only needed because test files (array-find.php and array-find-key.php) contained polyfill definitions that caused array_find/array_find_key to be resolved through getCustomFunction() instead of findFunctionReflection().

Fix:

  1. Removed polyfill definitions from tests/PHPStan/Analyser/nsrt/array-find.php and array-find-key.php, added // lint >= 8.4 so these tests only run on PHP 8.4+ where the native functions exist
  2. Removed the getFunctionPurityFromMetadata() method from NativeFunctionReflectionProvider
  3. Removed the metadata lookup call from BetterReflectionProvider::getCustomFunction()

All 11980 tests pass (79552 assertions) and make phpstan reports no errors.


return $this->functionReflectionFactory->create(
$reflectionFunction,
$templateTypeMap,
Expand Down
10 changes: 5 additions & 5 deletions src/Reflection/SignatureMap/FunctionSignatureMapProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class FunctionSignatureMapProvider implements SignatureMapProvider
/** @var array<string, mixed[]> */
private static array $signatureMaps = [];

/** @var array<string, array{hasSideEffects: bool}>|null */
/** @var array<string, array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}>|null */
private static ?array $functionMetadata = null;

public function __construct(
Expand Down Expand Up @@ -135,15 +135,15 @@ public function hasFunctionMetadata(string $name): bool
}

/**
* @return array{hasSideEffects: bool}
* @return array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}
*/
public function getMethodMetadata(string $className, string $methodName): array
{
return $this->getFunctionMetadata(sprintf('%s::%s', $className, $methodName));
}

/**
* @return array{hasSideEffects: bool}
* @return array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}
*/
public function getFunctionMetadata(string $functionName): array
{
Expand All @@ -157,12 +157,12 @@ public function getFunctionMetadata(string $functionName): array
}

/**
* @return array<string, array{hasSideEffects: bool}>
* @return array<string, array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}>
*/
private static function getFunctionMetadataMap(): array
{
if (self::$functionMetadata === null) {
/** @var array<string, array{hasSideEffects: bool}> $metadata */
/** @var array<string, array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}> $metadata */
$metadata = require __DIR__ . '/../../../resources/functionMetadata.php';
self::$functionMetadata = array_change_key_case($metadata, CASE_LOWER);
}
Expand Down
10 changes: 10 additions & 0 deletions src/Reflection/SignatureMap/NativeFunctionReflectionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,16 @@ public function findFunctionReflection(string $functionName): ?NativeFunctionRef
return $functionReflection;
}

public function getFunctionPurityFromMetadata(string $functionName): ?bool
{
$lowerCasedFunctionName = strtolower($functionName);
if ($this->signatureMapProvider->hasFunctionMetadata($lowerCasedFunctionName)) {
return !$this->signatureMapProvider->getFunctionMetadata($lowerCasedFunctionName)['hasSideEffects'];
}

return null;
}

private function getReturnTypeFromPhpDoc(ResolvedPhpDocBlock $phpDoc): ?Type
{
$returnTag = $phpDoc->getReturnTag();
Expand Down
4 changes: 2 additions & 2 deletions src/Reflection/SignatureMap/Php8SignatureMapProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -385,15 +385,15 @@ public function hasFunctionMetadata(string $name): bool
}

/**
* @return array{hasSideEffects: bool}
* @return array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}
*/
public function getMethodMetadata(string $className, string $methodName): array
{
return $this->functionSignatureMapProvider->getMethodMetadata($className, $methodName);
}

/**
* @return array{hasSideEffects: bool}
* @return array{hasSideEffects: bool, pureUnlessCallableIsImpure?: bool}
*/
public function getFunctionMetadata(string $functionName): array
{
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 @@ -26,12 +26,12 @@ public function hasMethodMetadata(string $className, string $methodName): bool;
public function hasFunctionMetadata(string $name): bool;

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

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

Expand Down
2 changes: 1 addition & 1 deletion tests/PHPStan/Command/data/file-without-errors.php
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<?php

array_filter([0, 1, 2]);
$a = array_filter([0, 1, 2]);
Loading
Loading