Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
60db01a
Parse @pure-unless-parameter-passed PHPDoc tag
zonuexe Jul 7, 2026
fdfad4f
Thread @pure-unless-parameter-passed through the reflection layer
zonuexe Jul 7, 2026
dd12523
Wire @pure-unless-parameter-passed into the analyser
zonuexe Jul 7, 2026
5328327
Mark by-ref out-parameter builtins as @pure-unless-parameter-passed
zonuexe Jul 7, 2026
dd88f4c
Add tests for @pure-unless-parameter-passed
zonuexe Jul 7, 2026
cd70c61
Cover the pre-PHP 8 parameter names for the by-ref out-parameter buil…
zonuexe Jul 7, 2026
d391af6
Honor @pure-unless-parameter-passed on constructor calls, unpacked an…
zonuexe Jul 8, 2026
64671b7
Parse @pure-unless-parameter-passed via phpdoc-parser 2.3.3
zonuexe Jul 8, 2026
ebbb858
Report certain impurity when a @pure-unless-parameter-passed argument…
zonuexe Jul 8, 2026
cb58bf7
Recognize @phpstan-pure-unless-callable-is-impure as a known tag
zonuexe Jul 9, 2026
99df900
Report @pure-unless-parameter-passed on a non-optional parameter
zonuexe Jul 9, 2026
f6f1f41
Expose @pure-unless-parameter-passed parameters as a function-level map
zonuexe Jul 9, 2026
2c8b6cb
Test @pure-unless-parameter-passed with a variadic parameter
zonuexe Jul 9, 2026
4ff7998
Mark preg_filter() as @pure-unless-parameter-passed
zonuexe Jul 9, 2026
431c09f
Combine the callable and parameter-passed purity verdicts
zonuexe Jul 9, 2026
c3953e5
Update bin/functionMetadata_original.php
zonuexe Jul 14, 2026
b7a61d6
Match arguments collected by a flagged variadic parameter
zonuexe Sep 10, 2026
e698108
Check the body of a @pure-unless-parameter-passed function
zonuexe Sep 10, 2026
a9400d4
Emit every purity condition when generating the function metadata
zonuexe Sep 10, 2026
74af498
Cover every @pure-unless-parameter-passed builtin mapping
zonuexe Sep 10, 2026
9303d78
Settle conditional purity where a callable value is invoked
zonuexe Sep 10, 2026
f60045e
Describe the function metadata entries with one optional-key shape
zonuexe Sep 12, 2026
aeb041b
Drop comments that restate the code
zonuexe Sep 12, 2026
29888c7
Merge the two conditional purity parameter mergers into one class
zonuexe Sep 12, 2026
9f785b3
Bump expected turbo version
zonuexe Oct 2, 2026
1b763ee
Match the generator's header example to the generated metadata
zonuexe Oct 3, 2026
84e2b0b
Leave the PCRE functions out of @pure-unless-parameter-passed
zonuexe Oct 3, 2026
492041b
undo change
staabm Oct 3, 2026
c8b500f
Bump expected turbo version
staabm Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions bin/functionMetadata_original.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@
* keyed by lowercase function name or "Class::method". resources/functionMetadata.php
* is generated from this file by bin/generate-function-metadata.php.
*
* Each entry is exactly one of these shapes:
* Each entry has one of these shapes:
*
* - ['hasSideEffects' => bool]
* false: the call is pure. true: the call has side effects.
* - ['pureUnlessCallableIsImpureParameters' => array<string, true>]
* the call is pure unless one of the listed callable parameters
* (keyed by parameter name) receives an impure callable, e.g. array_map()
* whose only side effects come from its 'callback' argument.
* - ['pureUnlessParameterPassedParameters' => array<string, true>]
* the call is pure unless one of the listed (by-ref out) optional parameters
* (keyed by parameter name) receives an argument, e.g. str_replace()
* whose only side effect is writing to its optional 'count' argument.
*
* The last two can be combined for a call that is pure unless either happens.
*/

/** @var array<string, array{hasSideEffects: bool}|array{pureUnlessCallableIsImpureParameters: array<string, bool>}> */
/** @var array<string, array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>, pureUnlessParameterPassedParameters?: array<string, bool>}> */
return [
'abs' => ['hasSideEffects' => false],
'acos' => ['hasSideEffects' => false],
Expand Down Expand Up @@ -265,13 +271,18 @@
'pclose' => ['hasSideEffects' => true],
'popen' => ['hasSideEffects' => true],
'preg_replace_callback' => ['pureUnlessCallableIsImpureParameters' => ['callback' => true]],
'similar_text' => ['pureUnlessParameterPassedParameters' => ['percent' => true]],
'readfile' => ['hasSideEffects' => true],
'rename' => ['hasSideEffects' => true],
'rewind' => ['hasSideEffects' => true],
'rmdir' => ['hasSideEffects' => true],
'sprintf' => ['hasSideEffects' => false],
'str_decrement' => ['hasSideEffects' => false],
'str_increment' => ['hasSideEffects' => false],
// 'count'/'replace_count': PHP 8+ uses the php-8-stubs parameter name, PHP <8 falls
// back to the legacy functionMap.php name.
'str_ireplace' => ['pureUnlessParameterPassedParameters' => ['count' => true, 'replace_count' => true]],
'str_replace' => ['pureUnlessParameterPassedParameters' => ['count' => true, 'replace_count' => true]],
'symlink' => ['hasSideEffects' => true],
'time' => ['hasSideEffects' => true],
'tempnam' => ['hasSideEffects' => true],
Expand Down
66 changes: 41 additions & 25 deletions bin/generate-function-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,18 +124,25 @@ public function enterNode(Node $node)
);
}

/** @var array<string, array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>}> $metadata */
/** @var array<string, array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>, pureUnlessParameterPassedParameters?: array<string, bool>}> $metadata */
$metadata = require __DIR__ . '/functionMetadata_original.php';
foreach ($visitor->functions as $functionName) {
if (array_key_exists($functionName, $metadata)) {
if (isset($metadata[$functionName]['hasSideEffects']) && $metadata[$functionName]['hasSideEffects']) {
throw new ShouldNotHappenException($functionName);
}

if (isset($metadata[$functionName]['pureUnlessCallableIsImpureParameters'])) {
$metadata[$functionName] = [
'pureUnlessCallableIsImpureParameters' => $metadata[$functionName]['pureUnlessCallableIsImpureParameters'],
];
$conditions = [];
foreach (['pureUnlessCallableIsImpureParameters', 'pureUnlessParameterPassedParameters'] as $conditionKey) {
if (!isset($metadata[$functionName][$conditionKey])) {
continue;
}

$conditions[$conditionKey] = $metadata[$functionName][$conditionKey];
}

if ($conditions !== []) {
$metadata[$functionName] = $conditions;

continue;
}
Expand Down Expand Up @@ -197,40 +204,49 @@ public function enterNode(Node $node)
* - ['pureUnlessCallableIsImpureParameters' => array<string, true>] - pure unless
* one of the listed callable parameters (keyed by parameter name) receives an
* impure callable, e.g. array_map()'s 'callback'.
* - ['pureUnlessParameterPassedParameters' => array<string, true>] - pure unless
* one of the listed (by-ref out) parameters (keyed by parameter name) receives
* an argument, e.g. str_replace()'s 'count'.
*/

/** @var array<string, array{hasSideEffects: bool}|array{pureUnlessCallableIsImpureParameters: array<string, bool>}> */
/** @var array<string, array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>, pureUnlessParameterPassedParameters?: array<string, bool>}> */
return [
%s
];
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(
static fn ($key, $param) => sprintf('%s => %s', $escape($key), $escape($param)),
array_keys($meta['pureUnlessCallableIsImpureParameters']),
$meta['pureUnlessCallableIsImpureParameters'],
),
$encodeParameterMap = static fn (array $parameters) => sprintf(
'[%s]',
implode(
', ',
array_map(
static fn ($key, $param) => sprintf('%s => %s', $escape($key), $escape($param)),
array_keys($parameters),
$parameters,
),
),
];
);

foreach ($metadata as $name => $meta) {
$entries = [];
if (isset($meta['hasSideEffects'])) {
$entries[] = sprintf('%s => %s', $escape('hasSideEffects'), $escape($meta['hasSideEffects']));
}
if (isset($meta['pureUnlessCallableIsImpureParameters'])) {
$entries[] = sprintf('%s => %s', $escape('pureUnlessCallableIsImpureParameters'), $encodeParameterMap($meta['pureUnlessCallableIsImpureParameters']));
}
if (isset($meta['pureUnlessParameterPassedParameters'])) {
$entries[] = sprintf('%s => %s', $escape('pureUnlessParameterPassedParameters'), $encodeParameterMap($meta['pureUnlessParameterPassedParameters']));
}
if ($entries === []) {
throw new ShouldNotHappenException($escape($meta));
}

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

Expand Down
12 changes: 9 additions & 3 deletions resources/functionMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@
* - ['pureUnlessCallableIsImpureParameters' => array<string, true>] - pure unless
* one of the listed callable parameters (keyed by parameter name) receives an
* impure callable, e.g. array_map()'s 'callback'.
* - ['pureUnlessParameterPassedParameters' => array<string, true>] - pure unless
* one of the listed (by-ref out) parameters (keyed by parameter name) receives
* an argument, e.g. str_replace()'s 'count'.
*/

/** @var array<string, array{hasSideEffects: bool}|array{pureUnlessCallableIsImpureParameters: array<string, bool>}> */
/** @var array<string, array{hasSideEffects?: bool, pureUnlessCallableIsImpureParameters?: array<string, bool>, pureUnlessParameterPassedParameters?: array<string, bool>}> */
return [
Comment thread
staabm marked this conversation as resolved.
'BackedEnum::from' => ['hasSideEffects' => false],
'BackedEnum::tryFrom' => ['hasSideEffects' => false],
Expand Down Expand Up @@ -777,10 +780,10 @@
'array_sum' => ['hasSideEffects' => false],
'array_udiff' => ['pureUnlessCallableIsImpureParameters' => ['data_comp_func' => true]],
'array_udiff_assoc' => ['pureUnlessCallableIsImpureParameters' => ['key_comp_func' => true]],
'array_udiff_uassoc' => ['pureUnlessCallableIsImpureParameters' => ['data_comp_func' => true ,'key_comp_func' => true]],
'array_udiff_uassoc' => ['pureUnlessCallableIsImpureParameters' => ['data_comp_func' => true, 'key_comp_func' => true]],
'array_uintersect' => ['pureUnlessCallableIsImpureParameters' => ['data_compare_func' => true]],
'array_uintersect_assoc' => ['pureUnlessCallableIsImpureParameters' => ['data_compare_func' => true]],
'array_uintersect_uassoc' => ['pureUnlessCallableIsImpureParameters' => ['data_compare_func' => true ,'key_compare_func' => true]],
'array_uintersect_uassoc' => ['pureUnlessCallableIsImpureParameters' => ['data_compare_func' => true, 'key_compare_func' => true]],
'array_unique' => ['hasSideEffects' => false],
'array_unshift' => ['hasSideEffects' => true],
'array_values' => ['hasSideEffects' => false],
Expand Down Expand Up @@ -1665,6 +1668,7 @@
'rtrim' => ['hasSideEffects' => false],
'sha1' => ['hasSideEffects' => false],
'sha1_file' => ['hasSideEffects' => true],
'similar_text' => ['pureUnlessParameterPassedParameters' => ['percent' => true]],
'sin' => ['hasSideEffects' => false],
'sinh' => ['hasSideEffects' => false],
'sizeof' => ['hasSideEffects' => false],
Expand All @@ -1679,8 +1683,10 @@
'str_ends_with' => ['hasSideEffects' => false],
'str_getcsv' => ['hasSideEffects' => false],
'str_increment' => ['hasSideEffects' => false],
'str_ireplace' => ['pureUnlessParameterPassedParameters' => ['count' => true, 'replace_count' => true]],
'str_pad' => ['hasSideEffects' => false],
'str_repeat' => ['hasSideEffects' => false],
'str_replace' => ['pureUnlessParameterPassedParameters' => ['count' => true, 'replace_count' => true]],
'str_rot13' => ['hasSideEffects' => false],
'str_split' => ['hasSideEffects' => false],
'str_starts_with' => ['hasSideEffects' => false],
Expand Down
8 changes: 7 additions & 1 deletion src/Analyser/ExprHandler/FuncCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$callableThrowPoints = array_values(array_filter($callableThrowPoints, static fn (InternalThrowPoint $throwPoint) => $throwPoint->isExplicit()));
}
$throwPoints = array_merge($throwPoints, $callableThrowPoints);
$impurePoints = array_merge($impurePoints, array_map(static fn (SimpleImpurePoint $impurePoint) => new ImpurePoint($scope, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()), $parametersAcceptor->getImpurePoints()));
// A callable value's impure points are resolved from its
// ParametersAcceptor alone, so a conditionally pure function reached
// through a first-class callable arrives here as an unconditional
// "possibly impure" point. The arguments are known at this call site,
// so settle the verdict here.
$callableImpurePoints = SimpleImpurePoint::narrowByConditionalPurity($parametersAcceptor->getImpurePoints(), $parametersAcceptor, $scope, $expr->getArgs());
$impurePoints = array_merge($impurePoints, array_map(static fn (SimpleImpurePoint $impurePoint) => new ImpurePoint($scope, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain()), $callableImpurePoints));

$scope = $this->closureProcessor->processImmediatelyCalledCallable($scope, $parametersAcceptor->getInvalidateExpressions(), $parametersAcceptor->getUsedVariables());
}
Expand Down
8 changes: 8 additions & 0 deletions src/Analyser/ExprHandler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,15 @@ private function getConstructorImpurePoints(?ExtendedMethodReflection $construct
}

$certain = $constructorReflection->isPure()->no();
// A constructor can carry both flags at once, so combine the verdicts
// the same way SimpleImpurePoint::createFromVariant() does for calls:
// Yes = pure, No = impure, Maybe = possibly impure.
$verdict = SimpleImpurePoint::resolvePureUnlessCallableIsImpureVerdict($parametersAcceptor, $scope, $expr->getArgs());
$passedVerdict = SimpleImpurePoint::resolvePureUnlessParameterPassedVerdict($parametersAcceptor, $expr->getArgs());
if ($passedVerdict !== null) {
$verdict = $verdict === null ? $passedVerdict : $verdict->and($passedVerdict);
}

if ($verdict !== null && $verdict->yes()) {
return [];
}
Expand Down
7 changes: 7 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2039,6 +2039,7 @@ public function enterTrait(ClassReflection $traitReflection): self
* @param array<string, bool> $immediatelyInvokedCallableParameters
* @param array<string, Type> $phpDocClosureThisTypeParameters
* @param array<string, bool> $phpDocPureUnlessCallableIsImpureParameters
* @param array<string, bool> $phpDocPureUnlessParameterPassedParameters
*/
public function enterClassMethod(
Node\Stmt\ClassMethod $classMethod,
Expand All @@ -2061,6 +2062,7 @@ public function enterClassMethod(
bool $isConstructor = false,
?ResolvedPhpDocBlock $resolvedPhpDocBlock = null,
array $phpDocPureUnlessCallableIsImpureParameters = [],
array $phpDocPureUnlessParameterPassedParameters = [],
): self
{
if (!$this->isInClass()) {
Expand Down Expand Up @@ -2097,6 +2099,7 @@ public function enterClassMethod(
$isConstructor,
$this->attributeReflectionFactory->fromAttrGroups($classMethod->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $classMethod)),
$phpDocPureUnlessCallableIsImpureParameters,
$phpDocPureUnlessParameterPassedParameters,
),
!$classMethod->isStatic(),
);
Expand Down Expand Up @@ -2188,6 +2191,7 @@ public function enterPropertyHook(
false,
$this->attributeReflectionFactory->fromAttrGroups($hook->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $hook)),
[],
[],
),
true,
);
Expand Down Expand Up @@ -2265,6 +2269,7 @@ private function getParameterAttributes(ClassMethod|Function_|PropertyHook $func
* @param array<string, bool> $immediatelyInvokedCallableParameters
* @param array<string, Type> $phpDocClosureThisTypeParameters
* @param array<string, bool> $pureUnlessCallableIsImpureParameters
* @param array<string, bool> $pureUnlessParameterPassedParameters
*/
public function enterFunction(
Node\Stmt\Function_ $function,
Expand All @@ -2283,6 +2288,7 @@ public function enterFunction(
array $immediatelyInvokedCallableParameters = [],
array $phpDocClosureThisTypeParameters = [],
array $pureUnlessCallableIsImpureParameters = [],
array $pureUnlessParameterPassedParameters = [],
): self
{
return $this->enterFunctionLike(
Expand All @@ -2309,6 +2315,7 @@ public function enterFunction(
$phpDocClosureThisTypeParameters,
$this->attributeReflectionFactory->fromAttrGroups($function->attrGroups, InitializerExprContext::fromStubParameter(null, $this->getFile(), $function)),
$pureUnlessCallableIsImpureParameters,
$pureUnlessParameterPassedParameters,
),
false,
);
Expand Down
6 changes: 4 additions & 2 deletions src/Analyser/PhpDocsResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function __construct(
}

/**
* @return array{TemplateTypeMap, array<string, Type>, array<string, bool>, array<string, Type>, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array<string, Type>, array<(string|int), VarTag>, bool, ?ResolvedPhpDocBlock, array<string, bool>}
* @return array{TemplateTypeMap, array<string, Type>, array<string, bool>, array<string, Type>, ?Type, ?Type, ?string, bool, bool, bool, bool|null, bool, bool, string|null, Assertions, ?Type, array<string, Type>, array<(string|int), VarTag>, bool, ?ResolvedPhpDocBlock, array<string, bool>, array<string, bool>}
*/
public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $node): array
{
Expand Down Expand Up @@ -73,6 +73,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
$functionName = null;
$phpDocParameterOutTypes = [];
$phpDocPureUnlessCallableIsImpureParameters = [];
$phpDocPureUnlessParameterPassedParameters = [];

if ($node instanceof Node\Stmt\ClassMethod) {
if (!$scope->isInClass()) {
Expand Down Expand Up @@ -207,6 +208,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
$selfOutType = $resolvedPhpDoc->getSelfOutTag() !== null ? $resolvedPhpDoc->getSelfOutTag()->getType() : null;
$varTags = $resolvedPhpDoc->getVarTags();
$phpDocPureUnlessCallableIsImpureParameters = $resolvedPhpDoc->getParamsPureUnlessCallableIsImpure();
$phpDocPureUnlessParameterPassedParameters = $resolvedPhpDoc->getParamsPureUnlessParameterPassed();
}

if ($acceptsNamedArguments && $scope->isInClass()) {
Expand Down Expand Up @@ -234,7 +236,7 @@ public function getPhpDocs(Scope $scope, Node\FunctionLike|Node\Stmt\Property $n
}
}

return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation, $resolvedPhpDoc, $phpDocPureUnlessCallableIsImpureParameters];
return [$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $docComment, $asserts, $selfOutType, $phpDocParameterOutTypes, $varTags, $isAllowedPrivateMutation, $resolvedPhpDoc, $phpDocPureUnlessCallableIsImpureParameters, $phpDocPureUnlessParameterPassedParameters];
}

private function getPhpDocReturnType(ResolvedPhpDocBlock $resolvedPhpDoc, Type $nativeReturnType): ?Type
Expand Down
3 changes: 2 additions & 1 deletion src/Analyser/StmtHandler/ClassMethodHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public function processStmt(
{
$dependencies = [];
$dependencies[] = $this->attributesHandler->processAttributeGroups($nodeScopeResolver, $stmt, $stmt->attrGroups, $scope, $storage, $nodeCallback);
[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $phpDocComment, $asserts, $selfOutType, $phpDocParameterOutTypes, , , , $pureUnlessCallableIsImpureParameters] = $this->phpDocsResolver->getPhpDocs($scope, $stmt);
[$templateTypeMap, $phpDocParameterTypes, $phpDocImmediatelyInvokedCallableParameters, $phpDocClosureThisTypeParameters, $phpDocReturnType, $phpDocThrowType, $deprecatedDescription, $isDeprecated, $isInternal, $isFinal, $isPure, $acceptsNamedArguments, $isReadOnly, $phpDocComment, $asserts, $selfOutType, $phpDocParameterOutTypes, , , , $pureUnlessCallableIsImpureParameters, $pureUnlessParameterPassedParameters] = $this->phpDocsResolver->getPhpDocs($scope, $stmt);

$dependencies[] = $this->parametersProcessor->processParams($nodeScopeResolver, $stmt, $stmt->params, $scope, $storage, $nodeCallback);

Expand Down Expand Up @@ -112,6 +112,7 @@ public function processStmt(
$isConstructor,
null,
$pureUnlessCallableIsImpureParameters,
$pureUnlessParameterPassedParameters,
);

if (!$scope->isInClass()) {
Expand Down
Loading
Loading