Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8bf7637
Parse @pure-unless-parameter-passed PHPDoc tag
zonuexe Jul 7, 2026
830a2f4
Thread @pure-unless-parameter-passed through the reflection layer
zonuexe Jul 7, 2026
0600994
Wire @pure-unless-parameter-passed into the analyser
zonuexe Jul 7, 2026
e075de3
Mark by-ref out-parameter builtins as @pure-unless-parameter-passed
zonuexe Jul 7, 2026
3cdee85
Add tests for @pure-unless-parameter-passed
zonuexe Jul 7, 2026
9266d3c
Cover the pre-PHP 8 parameter names for the by-ref out-parameter buil…
zonuexe Jul 7, 2026
3d0b0a9
Honor @pure-unless-parameter-passed on constructor calls, unpacked an…
zonuexe Jul 8, 2026
a336616
Parse @pure-unless-parameter-passed via phpdoc-parser 2.3.3
zonuexe Jul 8, 2026
65a2a94
Report certain impurity when a @pure-unless-parameter-passed argument…
zonuexe Jul 8, 2026
390ecc2
Recognize @phpstan-pure-unless-callable-is-impure as a known tag
zonuexe Jul 9, 2026
0f992f5
Report @pure-unless-parameter-passed on a non-optional parameter
zonuexe Jul 9, 2026
ae33471
Expose @pure-unless-parameter-passed parameters as a function-level map
zonuexe Jul 9, 2026
ac502b8
Test @pure-unless-parameter-passed with a variadic parameter
zonuexe Jul 9, 2026
2823c48
Mark preg_filter() as @pure-unless-parameter-passed
zonuexe Jul 9, 2026
39e3978
Combine the callable and parameter-passed purity verdicts
zonuexe Jul 9, 2026
3fe1ca5
Update bin/functionMetadata_original.php
zonuexe Jul 14, 2026
07535c7
Match arguments collected by a flagged variadic parameter
zonuexe Sep 10, 2026
eb61ac2
Check the body of a @pure-unless-parameter-passed function
zonuexe Sep 10, 2026
53c2e5b
Emit every purity condition when generating the function metadata
zonuexe Sep 10, 2026
1cee42d
Cover every @pure-unless-parameter-passed builtin mapping
zonuexe Sep 10, 2026
a3bdef2
Settle conditional purity where a callable value is invoked
zonuexe Sep 10, 2026
9f5cb16
Describe the function metadata entries with one optional-key shape
zonuexe Sep 12, 2026
98b2691
Drop comments that restate the code
zonuexe Sep 12, 2026
575a810
Merge the two conditional purity parameter mergers into one class
zonuexe Sep 12, 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
24 changes: 21 additions & 3 deletions bin/functionMetadata_original.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@
* 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,
* e.g. preg_replace_callback() (impure callback or a passed '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 [
'abs' => ['hasSideEffects' => false],
'acos' => ['hasSideEffects' => false],
Expand Down Expand Up @@ -264,14 +271,25 @@
'output_reset_rewrite_vars' => ['hasSideEffects' => true],
'pclose' => ['hasSideEffects' => true],
'popen' => ['hasSideEffects' => true],
'preg_replace_callback' => ['pureUnlessCallableIsImpureParameters' => ['callback' => true]],
'preg_filter' => ['pureUnlessParameterPassedParameters' => ['count' => true]],
// 'matches'/'subpatterns': PHP 8+ uses the php-8-stubs parameter name, PHP <8 falls
// back to the legacy functionMap.php name.
'preg_match' => ['pureUnlessParameterPassedParameters' => ['matches' => true, 'subpatterns' => true]],
'preg_match_all' => ['pureUnlessParameterPassedParameters' => ['matches' => true, 'subpatterns' => true]],
'preg_replace' => ['pureUnlessParameterPassedParameters' => ['count' => true]],
'preg_replace_callback' => ['pureUnlessCallableIsImpureParameters' => ['callback' => true], 'pureUnlessParameterPassedParameters' => ['count' => 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 'replace_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
18 changes: 14 additions & 4 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 @@ -1630,11 +1633,15 @@
'posix_ttyname' => ['hasSideEffects' => false],
'posix_uname' => ['hasSideEffects' => false],
'pow' => ['hasSideEffects' => false],
'preg_filter' => ['pureUnlessParameterPassedParameters' => ['count' => true]],
'preg_grep' => ['hasSideEffects' => false],
'preg_last_error' => ['hasSideEffects' => true],
'preg_last_error_msg' => ['hasSideEffects' => true],
'preg_match' => ['pureUnlessParameterPassedParameters' => ['matches' => true, 'subpatterns' => true]],
'preg_match_all' => ['pureUnlessParameterPassedParameters' => ['matches' => true, 'subpatterns' => true]],
'preg_quote' => ['hasSideEffects' => false],
'preg_replace_callback' => ['pureUnlessCallableIsImpureParameters' => ['callback' => true]],
'preg_replace' => ['pureUnlessParameterPassedParameters' => ['count' => true]],
'preg_replace_callback' => ['pureUnlessCallableIsImpureParameters' => ['callback' => true], 'pureUnlessParameterPassedParameters' => ['count' => true]],
'preg_split' => ['hasSideEffects' => false],
'property_exists' => ['hasSideEffects' => false],
'quoted_printable_decode' => ['hasSideEffects' => false],
Expand Down Expand Up @@ -1665,6 +1672,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 +1687,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 @@ -137,7 +137,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 = $nodeScopeResolver->processImmediatelyCalledCallable($scope, $parametersAcceptor->getInvalidateExpressions(), $parametersAcceptor->getUsedVariables());
}
Expand Down
9 changes: 9 additions & 0 deletions src/Analyser/ExprHandler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,13 +274,22 @@ private function processConstructorReflection(string $className, New_ $expr, Mut
if ($constructorReflection !== null) {
if (!$constructorReflection->hasSideEffects()->no()) {
$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 [$constructorReflection, $classReflection, $parametersAcceptor, $impurePoints];
}
if ($verdict !== null && $verdict->no()) {
$certain = true;
}

$impurePoints[] = new ImpurePoint(
$scope,
$expr,
Expand Down
7 changes: 7 additions & 0 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -1672,6 +1672,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 @@ -1694,6 +1695,7 @@ public function enterClassMethod(
bool $isConstructor = false,
?ResolvedPhpDocBlock $resolvedPhpDocBlock = null,
array $phpDocPureUnlessCallableIsImpureParameters = [],
array $phpDocPureUnlessParameterPassedParameters = [],
): self
{
if (!$this->isInClass()) {
Expand Down Expand Up @@ -1730,6 +1732,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 @@ -1821,6 +1824,7 @@ public function enterPropertyHook(
false,
$this->attributeReflectionFactory->fromAttrGroups($hook->attrGroups, InitializerExprContext::fromStubParameter($this->getClassReflection()->getName(), $this->getFile(), $hook)),
[],
[],
),
true,
);
Expand Down Expand Up @@ -1898,6 +1902,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 @@ -1916,6 +1921,7 @@ public function enterFunction(
array $immediatelyInvokedCallableParameters = [],
array $phpDocClosureThisTypeParameters = [],
array $pureUnlessCallableIsImpureParameters = [],
array $pureUnlessParameterPassedParameters = [],
): self
{
return $this->enterFunctionLike(
Expand All @@ -1942,6 +1948,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 @@ -40,7 +40,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 @@ -71,6 +71,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 @@ -205,6 +206,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 @@ -232,7 +234,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
Loading
Loading