Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion build/PHPStan/Build/RequiredPhpVersionVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ public function enterNode(Node $node): ?Node
}
}

if ($node instanceof Node\Expr\CallLike && $node->isPartialFunctionApplication() && !$node->isFirstClassCallable()) {
$this->require(static fn (PhpVersion $phpVersion) => $phpVersion->supportsPartialFunctionApplication(), 'partial function application', $node);
}

if ($node instanceof Node\Arg && $node->name !== null) {
$this->require(static fn (PhpVersion $phpVersion) => $phpVersion->supportsNamedArguments(), 'named arguments', $node);
}
Expand Down Expand Up @@ -211,7 +215,7 @@ private function findPhpVersion(callable $callable): int
{
$minorVersionIterator = new PhpMinorVersionIterator(
new PhpVersion(70400),
new PhpVersion(80500),
new PhpVersion(80600),
);

foreach ($minorVersionIterator as $phpVersion) {
Expand Down
6 changes: 5 additions & 1 deletion build/collision-detector.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"../tests/PHPStan/Levels/data/stubs/function.php",
"../tests/PHPStan/Rules/Properties/data/abstract-final-property-hook-parse-error.php",
"../tests/PHPStan/Rules/Properties/data/final-property-hooks.php",
"../tests/PHPStan/Rules/Cast/data/void-cast.php"
"../tests/PHPStan/Rules/Cast/data/void-cast.php",
"../tests/PHPStan/Analyser/nsrt/partial-function-application.php",
"../tests/PHPStan/Rules/DeadCode/data/partial-function-application-unused-private-method.php",
"../tests/PHPStan/Rules/Functions/data/partial-function-application.php",
"../tests/PHPStan/Rules/Functions/data/partial-function-application-php-versions.php"
]
}
12 changes: 6 additions & 6 deletions src/Analyser/ExprHandler/AssignHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ private function captureAssignedCallArgResults(Expr $assignedExpr, ExpressionRes
} elseif ($assignedExpr instanceof Expr\BinaryOp\Minus && $assignedExpr->left instanceof FuncCall) {
$call = $assignedExpr->left;
}
if ($call === null || $call->isFirstClassCallable()) {
if ($call === null || $call->isPartialFunctionApplication()) {
return [];
}

Expand Down Expand Up @@ -418,7 +418,7 @@ private function createSpecifyTypesCallback(Assign $expr, ExpressionResult $assi
if (
$expr->expr instanceof FuncCall
&& $expr->expr->name instanceof Name
&& !$expr->expr->isFirstClassCallable()
&& !$expr->expr->isPartialFunctionApplication()
&& in_array($expr->expr->name->toLowerString(), ['array_key_first', 'array_key_last'], true)
&& count($expr->expr->getArgs()) >= 1
) {
Expand Down Expand Up @@ -456,7 +456,7 @@ private function createSpecifyTypesCallback(Assign $expr, ExpressionResult $assi
if (
$expr->expr instanceof FuncCall
&& $expr->expr->name instanceof Name
&& !$expr->expr->isFirstClassCallable()
&& !$expr->expr->isPartialFunctionApplication()
&& count($expr->expr->getArgs()) >= 2
) {
$funcName = $expr->expr->name->toLowerString();
Expand Down Expand Up @@ -518,7 +518,7 @@ private function createSpecifyTypesCallback(Assign $expr, ExpressionResult $assi
if (
$expr->expr instanceof FuncCall
&& $expr->expr->name instanceof Name
&& !$expr->expr->isFirstClassCallable()
&& !$expr->expr->isPartialFunctionApplication()
&& in_array($expr->expr->name->toLowerString(), ['array_rand'], true)
&& count($expr->expr->getArgs()) >= 1
) {
Expand Down Expand Up @@ -549,7 +549,7 @@ private function createSpecifyTypesCallback(Assign $expr, ExpressionResult $assi
$expr->expr instanceof Expr\BinaryOp\Minus
&& $expr->expr->left instanceof FuncCall
&& $expr->expr->left->name instanceof Name
&& !$expr->expr->left->isFirstClassCallable()
&& !$expr->expr->left->isPartialFunctionApplication()
&& $expr->expr->right instanceof Node\Scalar\Int_
&& $expr->expr->right->value === 1
&& in_array($expr->expr->left->name->toLowerString(), ['count', 'sizeof'], true)
Expand Down Expand Up @@ -2450,7 +2450,7 @@ private function processInArrayForConditionalExpressionsAfterAssign(
if (
!$assignedExpr->name instanceof Name
|| $assignedExpr->name->toLowerString() !== 'in_array'
|| $assignedExpr->isFirstClassCallable()
|| $assignedExpr->isPartialFunctionApplication()
|| !$assignedType->isTrue()->maybe()
) {
return $conditionalExpressions;
Expand Down
14 changes: 7 additions & 7 deletions src/Analyser/ExprHandler/BinaryOpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
// never collects)
$specifySubResults = [];
$specifySubExprs = [];
if ($expr->right instanceof Expr\FuncCall && !$expr->right->isFirstClassCallable() && isset($expr->right->getArgs()[0])) {
if ($expr->right instanceof Expr\FuncCall && !$expr->right->isPartialFunctionApplication() && isset($expr->right->getArgs()[0])) {
$specifySubExprs[] = $expr->right->getArgs()[0]->value;
} elseif ($expr->right instanceof BinaryOp\Minus) {
$specifySubExprs[] = $expr->right->right;
if ($expr->right->left instanceof Expr\FuncCall && !$expr->right->left->isFirstClassCallable() && isset($expr->right->left->getArgs()[0])) {
if ($expr->right->left instanceof Expr\FuncCall && !$expr->right->left->isPartialFunctionApplication() && isset($expr->right->left->getArgs()[0])) {
$specifySubExprs[] = $expr->right->left->getArgs()[0]->value;
}
}
Expand Down Expand Up @@ -368,7 +368,7 @@ static function () use ($expr, $nativeTypesPromoted, $typeCallback): Type {
if (
$expr->left instanceof Expr\FuncCall
&& $expr->left->name instanceof Name
&& !$expr->left->isFirstClassCallable()
&& !$expr->left->isPartialFunctionApplication()
&& in_array(strtolower((string) $expr->left->name), ['count', 'sizeof', 'strlen', 'mb_strlen', 'preg_match'], true)
&& count($expr->left->getArgs()) >= 1
&& (
Expand Down Expand Up @@ -426,7 +426,7 @@ static function () use ($expr, $nativeTypesPromoted, $typeCallback): Type {
!$context->null()
&& $expr->right instanceof Expr\FuncCall
&& $expr->right->name instanceof Name
&& !$expr->right->isFirstClassCallable()
&& !$expr->right->isPartialFunctionApplication()
&& in_array(strtolower((string) $expr->right->name), ['count', 'sizeof'], true)
&& count($expr->right->getArgs()) >= 1
&& $leftType->isInteger()->yes()
Expand Down Expand Up @@ -530,7 +530,7 @@ static function () use ($expr, $nativeTypesPromoted, $typeCallback): Type {
&& $expr->right instanceof BinaryOp\Minus
&& $expr->right->left instanceof Expr\FuncCall
&& $expr->right->left->name instanceof Name
&& !$expr->right->left->isFirstClassCallable()
&& !$expr->right->left->isPartialFunctionApplication()
&& in_array(strtolower((string) $expr->right->left->name), ['count', 'sizeof'], true)
&& count($expr->right->left->getArgs()) >= 1
// constant offsets are handled via HasOffsetType/HasOffsetValueType
Expand All @@ -557,7 +557,7 @@ static function () use ($expr, $nativeTypesPromoted, $typeCallback): Type {
!$context->null()
&& $expr->right instanceof Expr\FuncCall
&& $expr->right->name instanceof Name
&& !$expr->right->isFirstClassCallable()
&& !$expr->right->isPartialFunctionApplication()
&& in_array(strtolower((string) $expr->right->name), ['preg_match'], true)
&& count($expr->right->getArgs()) >= 3
&& (
Expand All @@ -575,7 +575,7 @@ static function () use ($expr, $nativeTypesPromoted, $typeCallback): Type {
!$context->null()
&& $expr->right instanceof Expr\FuncCall
&& $expr->right->name instanceof Name
&& !$expr->right->isFirstClassCallable()
&& !$expr->right->isPartialFunctionApplication()
&& in_array(strtolower((string) $expr->right->name), ['strlen', 'mb_strlen'], true)
&& count($expr->right->getArgs()) === 1
&& $leftType->isInteger()->yes()
Expand Down
4 changes: 2 additions & 2 deletions src/Analyser/ExprHandler/FuncCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public function __construct(

public function supports(Expr $expr): bool
{
return $expr instanceof FuncCall && !$expr->isFirstClassCallable();
return $expr instanceof FuncCall && !$expr->isPartialFunctionApplication();
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
Expand Down Expand Up @@ -468,7 +468,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
// for find_key: an empty result does not mean an empty array)
if (
$expr->name instanceof Name
&& !$expr->isFirstClassCallable()
&& !$expr->isPartialFunctionApplication()
&& isset($normalizedExpr->getArgs()[0])
&& $type->isNull()->yes()
) {
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/Helper/DefaultNarrowingHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function __construct(
*/
public function specifyTypesForNode(Scope $scope, Expr $node, TypeSpecifierContext $context): SpecifiedTypes
{
if ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) {
if ($node instanceof Expr\CallLike && $node->isPartialFunctionApplication()) {
return (new SpecifiedTypes([], []))->setRootExpr($node);
}

Expand Down
10 changes: 5 additions & 5 deletions src/Analyser/ExprHandler/Helper/IdenticalNarrowingHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,8 @@ private function specifyGeneral(
// one non-empty side makes both non-empty
if (
$context->true()
&& $unwrappedLeft->name instanceof Name && in_array($unwrappedLeft->name->toLowerString(), ['count', 'sizeof'], true) && !$unwrappedLeft->isFirstClassCallable() && isset($unwrappedLeft->getArgs()[0])
&& $unwrappedRight->name instanceof Name && in_array($unwrappedRight->name->toLowerString(), ['count', 'sizeof'], true) && !$unwrappedRight->isFirstClassCallable() && isset($unwrappedRight->getArgs()[0])
&& $unwrappedLeft->name instanceof Name && in_array($unwrappedLeft->name->toLowerString(), ['count', 'sizeof'], true) && !$unwrappedLeft->isPartialFunctionApplication() && isset($unwrappedLeft->getArgs()[0])
&& $unwrappedRight->name instanceof Name && in_array($unwrappedRight->name->toLowerString(), ['count', 'sizeof'], true) && !$unwrappedRight->isPartialFunctionApplication() && isset($unwrappedRight->getArgs()[0])
) {
if ($leftArgResult === null || $rightArgResult === null) {
return null;
Expand Down Expand Up @@ -795,7 +795,7 @@ private function specifyEqualAgainstConstantSide(
if (
$unwrappedSubject instanceof Expr\FuncCall
&& $unwrappedSubject->name instanceof Name
&& !$unwrappedSubject->isFirstClassCallable()
&& !$unwrappedSubject->isPartialFunctionApplication()
&& isset($unwrappedSubject->getArgs()[0])
) {
$funcName = $unwrappedSubject->name->toLowerString();
Expand Down Expand Up @@ -837,7 +837,7 @@ private function specifyFuncCallFamilies(
?ExpressionResult $argResult,
): SpecifiedTypes|false|null
{
if (!($call->name instanceof Name) || $call->isFirstClassCallable() || !isset($call->getArgs()[0])) {
if (!($call->name instanceof Name) || $call->isPartialFunctionApplication() || !isset($call->getArgs()[0])) {
return false;
}

Expand Down Expand Up @@ -1115,7 +1115,7 @@ private function specifyFuncCallFamilies(
public function captureFirstArgResult(Expr $side, ExpressionResultStorage $storage): ?ExpressionResult
{
$unwrapped = $side instanceof AlwaysRememberedExpr ? $side->getExpr() : $side;
if (!$unwrapped instanceof Expr\FuncCall || $unwrapped->isFirstClassCallable() || !isset($unwrapped->getArgs()[0])) {
if (!$unwrapped instanceof Expr\FuncCall || $unwrapped->isPartialFunctionApplication() || !isset($unwrapped->getArgs()[0])) {
return null;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/MethodCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function __construct(

public function supports(Expr $expr): bool
{
return $expr instanceof MethodCall && !$expr->isFirstClassCallable();
return $expr instanceof MethodCall && !$expr->isPartialFunctionApplication();
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public function __construct(

public function supports(Expr $expr): bool
{
return $expr instanceof New_ && !$expr->isFirstClassCallable();
return $expr instanceof New_ && !$expr->isPartialFunctionApplication();
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/NullsafeMethodCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function __construct(

public function supports(Expr $expr): bool
{
return $expr instanceof NullsafeMethodCall;
return $expr instanceof NullsafeMethodCall && !$expr->isPartialFunctionApplication();
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
Expand Down
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandler/StaticCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public function __construct(

public function supports(Expr $expr): bool
{
return $expr instanceof StaticCall && !$expr->isFirstClassCallable();
return $expr instanceof StaticCall && !$expr->isPartialFunctionApplication();
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser\ExprHandler\Virtual;

use Closure;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Stmt;
use PHPStan\Analyser\ExpressionContext;
use PHPStan\Analyser\ExpressionResult;
use PHPStan\Analyser\ExpressionResultFactory;
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\DefaultNarrowingHelper;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Analyser\VariableFlow;
use PHPStan\Dependency\Dependencies;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\PartialFunctionApplicationNode;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use function array_merge;

/**
* The bound arguments of a partial function application are evaluated when
* the closure is created, so they are processed here, after the callee.
*
* @implements ExprHandler<PartialFunctionApplicationNode>
*/
#[AutowiredService]
final class PartialFunctionApplicationNodeHandler implements ExprHandler
{

public function __construct(
private ExpressionResultFactory $expressionResultFactory,
private DefaultNarrowingHelper $defaultNarrowingHelper,
)
{
}

public function supports(Expr $expr): bool
{
return $expr instanceof PartialFunctionApplicationNode;
}

public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$beforeScope = $scope;
$hasYield = false;
$isAlwaysTerminating = false;
$throwPoints = [];
$impurePoints = [];
$variableFlows = [];
$dependencies = [];
foreach ($this->getEvaluatedExprs($expr->getOriginalNode()) as $evaluatedExpr) {
$result = $nodeScopeResolver->processExprNode($stmt, $evaluatedExpr, $scope, $storage, $nodeCallback, ExpressionContext::createDeep($context->shouldResolveTemplateArguments()));
$scope = $result->getScope();
$hasYield = $hasYield || $result->hasYield();
$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating();
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
$variableFlows[] = $result->getVariableFlow();
$dependencies[] = $result->getDependencies();
}

$result = $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
expr: $expr,
variableFlow: VariableFlow::sequence(...$variableFlows),
hasYield: $hasYield,
isAlwaysTerminating: $isAlwaysTerminating,
throwPoints: $throwPoints,
impurePoints: $impurePoints,
typeCallback: static fn (bool $nativeTypesPromoted): Type => new ObjectType(Closure::class),
specifyTypesCallback: fn (TypeSpecifierContext $context, bool $nativeTypesPromoted) => $this->defaultNarrowingHelper->specifyDefaultTypes($expr, $context),
);

return $result->withDependencies(Dependencies::merge(...$dependencies));
}

/**
* The callee's own expressions and the bound argument values, in the order
* PHP evaluates them.
*
* @return list<Expr>
*/
private function getEvaluatedExprs(Expr\CallLike $call): array
{
$exprs = [];
if ($call instanceof Expr\FuncCall) {
if ($call->name instanceof Expr) {
$exprs[] = $call->name;
}
} elseif ($call instanceof Expr\MethodCall || $call instanceof Expr\NullsafeMethodCall) {
$exprs[] = $call->var;
if ($call->name instanceof Expr) {
$exprs[] = $call->name;
}
} elseif ($call instanceof Expr\StaticCall) {
if ($call->class instanceof Expr) {
$exprs[] = $call->class;
}
if ($call->name instanceof Expr) {
$exprs[] = $call->name;
}
} elseif ($call instanceof Expr\New_) {
if ($call->class instanceof Expr) {
$exprs[] = $call->class;
}
}

foreach ($call->getRawArgs() as $arg) {
if (!$arg instanceof Arg) {
continue;
}

$exprs[] = $arg->value;
}

return $exprs;
}

}
2 changes: 1 addition & 1 deletion src/Analyser/ExprHandlerRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static function resolve(Expr $expr, Container $container): ?ExprHandler
{
$cacheKey = get_class($expr);
if ($expr instanceof Expr\CallLike) {
$cacheKey .= '|' . $expr->isFirstClassCallable();
$cacheKey .= '|' . $expr->isPartialFunctionApplication();
if ($expr instanceof Expr\New_) {
$cacheKey .= '|' . get_class($expr->class);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Analyser/ExpressionResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -527,13 +527,13 @@ private function projectsVoidToNull(bool $nativeTypesPromoted): bool

$expr = $this->expr;
if ($expr instanceof Expr\FuncCall) {
return $expr->name instanceof Node\Name && !$expr->isFirstClassCallable();
return $expr->name instanceof Node\Name && !$expr->isPartialFunctionApplication();
}

return ($expr instanceof Expr\MethodCall
|| $expr instanceof Expr\NullsafeMethodCall
|| $expr instanceof Expr\StaticCall)
&& !$expr->isFirstClassCallable();
&& !$expr->isPartialFunctionApplication();
}

/**
Expand Down
Loading
Loading