Skip to content
2,451 changes: 2,451 additions & 0 deletions resources/constantToFunctionParameterMap.php

Large diffs are not rendered by default.

55 changes: 55 additions & 0 deletions src/Reflection/AllowedConstantsResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

/**
* Result of checking constants passed to a parameter against its allowed set.
*
* Returned by ExtendedParameterReflection::checkAllowedConstants(). Reports
* three kinds of problems: constants not in the allowed list, mutually exclusive
* constants combined in a bitmask, and bitmask usage on a single-value parameter.
*
* @api
*/
final class AllowedConstantsResult
{

/**
* @param list<ConstantReflection> $disallowedConstants
* @param list<list<string>> $violatedExclusiveGroups
*/
public function __construct(
private array $disallowedConstants,
private array $violatedExclusiveGroups,
private bool $bitmaskNotAllowed,
)
{
}

public function isOk(): bool
{
return $this->disallowedConstants === [] && $this->violatedExclusiveGroups === [] && !$this->bitmaskNotAllowed;
}

public function isBitmaskNotAllowed(): bool
{
return $this->bitmaskNotAllowed;
}

/**
* @return list<ConstantReflection>
*/
public function getDisallowedConstants(): array
{
return $this->disallowedConstants;
}

/**
* @return list<list<string>>
*/
public function getViolatedExclusiveGroups(): array
{
return $this->violatedExclusiveGroups;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace PHPStan\Reflection\Annotations;

use PHPStan\Reflection\AllowedConstantsResult;
use PHPStan\Reflection\ExtendedParameterReflection;
use PHPStan\Reflection\ParameterAllowedConstants;
use PHPStan\Reflection\PassedByReference;
use PHPStan\TrinaryLogic;
use PHPStan\Type\MixedType;
Expand Down Expand Up @@ -80,4 +82,14 @@ public function getAttributes(): array
return [];
}

public function getAllowedConstants(): ?ParameterAllowedConstants
{
return null;
}

public function checkAllowedConstants(array $constants): AllowedConstantsResult
{
return new AllowedConstantsResult([], [], false);
}

}
7 changes: 7 additions & 0 deletions src/Reflection/ExtendedParameterReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,11 @@ public function getClosureThisType(): ?Type;
*/
public function getAttributes(): array;

public function getAllowedConstants(): ?ParameterAllowedConstants;

/**
* @param list<ConstantReflection> $constants Global and/or class constant reflections
*/
public function checkAllowedConstants(array $constants): AllowedConstantsResult;

}
1 change: 1 addition & 0 deletions src/Reflection/GenericParametersAcceptorResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ public static function resolve(array $argTypes, ParametersAcceptor $parametersAc
TrinaryLogic::createMaybe(),
null,
[],
null,
), $parameters),
$parametersAcceptor->isVariadic(),
$returnType,
Expand Down
17 changes: 17 additions & 0 deletions src/Reflection/Native/ExtendedNativeParameterReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace PHPStan\Reflection\Native;

use PHPStan\Reflection\AllowedConstantsResult;
use PHPStan\Reflection\AttributeReflection;
use PHPStan\Reflection\ExtendedParameterReflection;
use PHPStan\Reflection\ParameterAllowedConstants;
use PHPStan\Reflection\PassedByReference;
use PHPStan\TrinaryLogic;
use PHPStan\Type\MixedType;
Expand All @@ -28,6 +30,7 @@ public function __construct(
private TrinaryLogic $immediatelyInvokedCallable,
private ?Type $closureThisType,
private array $attributes,
private ?ParameterAllowedConstants $allowedConstants,
)
{
}
Expand Down Expand Up @@ -97,4 +100,18 @@ public function getAttributes(): array
return $this->attributes;
}

public function getAllowedConstants(): ?ParameterAllowedConstants
{
return $this->allowedConstants;
}

public function checkAllowedConstants(array $constants): AllowedConstantsResult
{
if ($this->allowedConstants === null) {
return new AllowedConstantsResult([], [], false);
}

return $this->allowedConstants->check($constants);
}

}
103 changes: 103 additions & 0 deletions src/Reflection/ParameterAllowedConstants.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

use function count;
use function in_array;

/**
* Describes which constants a function/method parameter accepts.
*
* Parameters are either 'single' (exactly one constant, e.g. `array_unique($flags)`)
* or 'bitmask' (constants combinable with `|`, e.g. `json_encode($flags)`).
* Bitmask parameters may have exclusive groups — subsets of constants
* that are mutually exclusive even within a bitmask.
*
* Populated from resources/constantToFunctionParameterMap.php and
* available via ExtendedParameterReflection::getAllowedConstants().
*
* @api
*/
final class ParameterAllowedConstants
{

/**
* @param 'single'|'bitmask' $type
* @param list<string> $constants
* @param list<list<string>> $exclusiveGroups
*/
public function __construct(
private string $type,
private array $constants,
private array $exclusiveGroups,
)
{
}

public function isBitmask(): bool
{
return $this->type === 'bitmask';
}

/**
* @return list<list<string>>
*/
public function getExclusiveGroups(): array
{
return $this->exclusiveGroups;
}

private function resolveConstantName(ConstantReflection $constant): string
{
if ($constant instanceof ClassConstantReflection) {
return $constant->getDeclaringClass()->getName() . '::' . $constant->getName();
}

return $constant->getName();
}

/**
* @param list<ConstantReflection> $constants
*/
public function check(array $constants): AllowedConstantsResult
{
$bitmaskNotAllowed = !$this->isBitmask() && count($constants) > 1;

$disallowed = [];
$names = [];

foreach ($constants as $constant) {
$name = $this->resolveConstantName($constant);
$names[] = $name;

if (in_array($name, $this->constants, true)) {
continue;
}

$disallowed[] = $constant;
}

$violated = [];
if ($this->isBitmask()) {
foreach ($this->exclusiveGroups as $group) {
$matched = [];
foreach ($names as $name) {
if (!in_array($name, $group, true)) {
continue;
}

$matched[] = $name;
}

if (count($matched) < 2) {
continue;
}

$violated[] = $matched;
}
}

return new AllowedConstantsResult($disallowed, $violated, $bitmaskNotAllowed);
}

}
50 changes: 50 additions & 0 deletions src/Reflection/ParameterAllowedConstantsMapProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php declare(strict_types = 1);

namespace PHPStan\Reflection;

use PHPStan\DependencyInjection\AutowiredService;

#[AutowiredService]
final class ParameterAllowedConstantsMapProvider
{

/** @var array<string, array<string, array{type: string, constants: list<string>, exclusiveGroups?: list<list<string>>}>>|null */
private ?array $map = null;

public function getForFunctionParameter(string $functionName, string $parameterName): ?ParameterAllowedConstants
{
return $this->get($functionName, $parameterName);
}

public function getForMethodParameter(string $className, string $methodName, string $parameterName): ?ParameterAllowedConstants
{
return $this->get($className . '::' . $methodName, $parameterName);
}

private function get(string $key, string $parameterName): ?ParameterAllowedConstants
{
$map = $this->getMap();

if (!isset($map[$key][$parameterName])) {
return null;
}

/** @var array{type: 'single'|'bitmask', constants: list<string>, exclusiveGroups?: list<list<string>>} $config */
$config = $map[$key][$parameterName];

return new ParameterAllowedConstants(
$config['type'],
$config['constants'],
$config['exclusiveGroups'] ?? [],
);
}

/**
* @return array<string, array<string, array{type: string, constants: list<string>, exclusiveGroups?: list<list<string>>}>>
*/
private function getMap(): array
{
return $this->map ??= require __DIR__ . '/../../resources/constantToFunctionParameterMap.php';
}

}
3 changes: 3 additions & 0 deletions src/Reflection/ParametersAcceptorSelector.php
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$parameter instanceof ExtendedParameterReflection ? $parameter->isImmediatelyInvokedCallable() : TrinaryLogic::createMaybe(),
$parameter instanceof ExtendedParameterReflection ? $parameter->getClosureThisType() : null,
$parameter instanceof ExtendedParameterReflection ? $parameter->getAttributes() : [],
$parameter instanceof ExtendedParameterReflection ? $parameter->getAllowedConstants() : null,
);
continue;
}
Expand Down Expand Up @@ -830,6 +831,7 @@ public static function combineAcceptors(array $acceptors): ExtendedParametersAcc
$immediatelyInvokedCallable,
$closureThisType,
$attributes,
null,
);

if ($isVariadic) {
Expand Down Expand Up @@ -928,6 +930,7 @@ private static function wrapParameter(ParameterReflection $parameter): ExtendedP
TrinaryLogic::createMaybe(),
null,
[],
null,
);
}

Expand Down
1 change: 1 addition & 0 deletions src/Reflection/Php/ClosureCallMethodReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public function getVariants(): array
TrinaryLogic::createMaybe(),
null,
[],
null,
), $parameters),
$this->closureType->isVariadic(),
$this->closureType->getReturnType(),
Expand Down
1 change: 1 addition & 0 deletions src/Reflection/Php/ExitFunctionReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ public function getVariants(): array
TrinaryLogic::createNo(),
null,
[],
null,
),
],
false,
Expand Down
17 changes: 17 additions & 0 deletions src/Reflection/Php/ExtendedDummyParameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

namespace PHPStan\Reflection\Php;

use PHPStan\Reflection\AllowedConstantsResult;
use PHPStan\Reflection\AttributeReflection;
use PHPStan\Reflection\ExtendedParameterReflection;
use PHPStan\Reflection\ParameterAllowedConstants;
use PHPStan\Reflection\PassedByReference;
use PHPStan\TrinaryLogic;
use PHPStan\Type\MixedType;
Expand All @@ -28,6 +30,7 @@ public function __construct(
private TrinaryLogic $immediatelyInvokedCallable,
private ?Type $closureThisType,
private array $attributes,
private ?ParameterAllowedConstants $allowedConstants,
)
{
parent::__construct($name, $type, $optional, $passedByReference, $variadic, $defaultValue);
Expand Down Expand Up @@ -68,4 +71,18 @@ public function getAttributes(): array
return $this->attributes;
}

public function getAllowedConstants(): ?ParameterAllowedConstants
{
return $this->allowedConstants;
}

public function checkAllowedConstants(array $constants): AllowedConstantsResult
{
if ($this->allowedConstants === null) {
return new AllowedConstantsResult([], [], false);
}

return $this->allowedConstants->check($constants);
}

}
Loading
Loading