Skip to content

Commit

Permalink
Throw points in CallableParametersAcceptor
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 18, 2024
1 parent 29c2f8b commit 74c02de
Show file tree
Hide file tree
Showing 19 changed files with 468 additions and 24 deletions.
82 changes: 77 additions & 5 deletions src/Analyser/MutatingScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@
use PHPStan\Parser\NewAssignedToPropertyVisitor;
use PHPStan\Parser\Parser;
use PHPStan\Php\PhpVersion;
use PHPStan\PhpDoc\Tag\TemplateTag;
use PHPStan\Reflection\Assertions;
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
use PHPStan\Reflection\Callables\SimpleThrowPoint;
use PHPStan\Reflection\ClassMemberReflection;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ConstantReflection;
Expand Down Expand Up @@ -1128,8 +1131,10 @@ private function resolveType(string $exprString, Expr $node): Type
if ($node instanceof FuncCall) {
if ($node->name instanceof Name) {
if ($this->reflectionProvider->hasFunction($node->name, $this)) {
$function = $this->reflectionProvider->getFunction($node->name, $this);
return $this->createFirstClassCallable(
$this->reflectionProvider->getFunction($node->name, $this)->getVariants(),
$function,
$function->getVariants(),
);
}

Expand All @@ -1142,6 +1147,7 @@ private function resolveType(string $exprString, Expr $node): Type
}

return $this->createFirstClassCallable(
null,
$callableType->getCallableParametersAcceptors($this),
);
}
Expand All @@ -1157,7 +1163,10 @@ private function resolveType(string $exprString, Expr $node): Type
return new ObjectType(Closure::class);
}

return $this->createFirstClassCallable($method->getVariants());
return $this->createFirstClassCallable(
$method,
$method->getVariants(),
);
}

if ($node instanceof Expr\StaticCall) {
Expand All @@ -1175,7 +1184,11 @@ private function resolveType(string $exprString, Expr $node): Type
return new ObjectType(Closure::class);
}

return $this->createFirstClassCallable($classType->getMethod($methodName, $this)->getVariants());
$method = $classType->getMethod($methodName, $this);
return $this->createFirstClassCallable(
$method,
$method->getVariants(),
);
}

if ($node instanceof New_) {
Expand Down Expand Up @@ -1263,12 +1276,22 @@ private function resolveType(string $exprString, Expr $node): Type
$returnType = TypehintHelper::decideType($this->getFunctionType($node->returnType, false, false), $returnType);
}
}

$arrowFunctionExprResult = $this->nodeScopeResolver->processExprNode(
new Node\Stmt\Expression($node->expr),
$node->expr,
$arrowScope,
static function (): void {
},
ExpressionContext::createDeep(),
);
$throwPoints = $arrowFunctionExprResult->getThrowPoints();
} else {
$closureScope = $this->enterAnonymousFunctionWithoutReflection($node, $callableParameters);
$closureReturnStatements = [];
$closureYieldStatements = [];
$closureExecutionEnds = [];
$this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $closureScope, static function (Node $node, Scope $scope) use ($closureScope, &$closureReturnStatements, &$closureYieldStatements, &$closureExecutionEnds): void {
$closureStatementResult = $this->nodeScopeResolver->processStmtNodes($node, $node->stmts, $closureScope, static function (Node $node, Scope $scope) use ($closureScope, &$closureReturnStatements, &$closureYieldStatements, &$closureExecutionEnds): void {
if ($scope->getAnonymousFunctionReflection() !== $closureScope->getAnonymousFunctionReflection()) {
return;
}
Expand Down Expand Up @@ -1303,6 +1326,8 @@ private function resolveType(string $exprString, Expr $node): Type
$closureYieldStatements[] = [$node, $scope];
}, StatementContext::createTopLevel());

$throwPoints = $closureStatementResult->getThrowPoints();

$returnTypes = [];
$hasNull = false;
foreach ($closureReturnStatements as [$returnNode, $returnScope]) {
Expand Down Expand Up @@ -1370,6 +1395,11 @@ private function resolveType(string $exprString, Expr $node): Type
$parameters,
$returnType,
$isVariadic,
TemplateTypeMap::createEmpty(),
TemplateTypeMap::createEmpty(),
TemplateTypeVarianceMap::createEmpty(),
[],
array_map(static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit() ? SimpleThrowPoint::createExplicit($throwPoint->getType(), $throwPoint->canContainAnyThrowable()) : SimpleThrowPoint::createImplicit(), $throwPoints),
);
} elseif ($node instanceof New_) {
if ($node->class instanceof Name) {
Expand Down Expand Up @@ -2192,14 +2222,54 @@ private function issetCheckUndefined(Expr $expr): ?bool
/**
* @param ParametersAcceptor[] $variants
*/
private function createFirstClassCallable(array $variants): Type
private function createFirstClassCallable(
FunctionReflection|ExtendedMethodReflection|null $function,
array $variants,
): Type
{
$closureTypes = [];

foreach ($variants as $variant) {
$returnType = $variant->getReturnType();
if ($variant instanceof ParametersAcceptorWithPhpDocs) {
$returnType = $this->nativeTypesPromoted ? $variant->getNativeReturnType() : $returnType;
}

$templateTags = [];
foreach ($variant->getTemplateTypeMap()->getTypes() as $templateType) {
if (!$templateType instanceof TemplateType) {
continue;
}
$templateTags[$templateType->getName()] = new TemplateTag(
$templateType->getName(),
$templateType->getBound(),
$templateType->getVariance(),
);
}

$throwPoints = [];
if ($variant instanceof CallableParametersAcceptor) {
$throwPoints = $variant->getThrowPoints();
} elseif ($function !== null) {
$returnTypeForThrow = $variant->getReturnType();
$throwType = $function->getThrowType();
if ($throwType === null) {
if ($returnTypeForThrow instanceof NeverType && $returnTypeForThrow->isExplicit()) {
$throwType = new ObjectType(Throwable::class);
}
}

if ($throwType !== null) {
if (!$throwType->isVoid()->yes()) {
$throwPoints[] = SimpleThrowPoint::createExplicit($throwType, true);
}
} else {
if (!(new ObjectType(Throwable::class))->isSuperTypeOf($returnTypeForThrow)->yes()) {
$throwPoints[] = SimpleThrowPoint::createImplicit();
}
}
}

$parameters = $variant->getParameters();
$closureTypes[] = new ClosureType(
$parameters,
Expand All @@ -2208,6 +2278,8 @@ private function createFirstClassCallable(array $variants): Type
$variant->getTemplateTypeMap(),
$variant->getResolvedTemplateTypeMap(),
$variant instanceof ParametersAcceptorWithPhpDocs ? $variant->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
$templateTags,
$throwPoints,
);
}

Expand Down
43 changes: 35 additions & 8 deletions src/Analyser/NodeScopeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
use PHPStan\PhpDoc\StubPhpDocProvider;
use PHPStan\PhpDoc\Tag\VarTag;
use PHPStan\Reflection\Assertions;
use PHPStan\Reflection\Callables\CallableParametersAcceptor;
use PHPStan\Reflection\Callables\SimpleThrowPoint;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Reflection\FunctionReflection;
Expand Down Expand Up @@ -2160,6 +2162,9 @@ static function (): void {
$context->enterDeep(),
);
$throwPoints = array_merge($throwPoints, $invokeResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $invokeResult->getImpurePoints());
} elseif ($parametersAcceptor instanceof CallableParametersAcceptor) {
$throwPoints = array_merge($throwPoints, array_map(static fn (SimpleThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $expr, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $expr), $parametersAcceptor->getThrowPoints()));
}
$impurePoints[] = new ImpurePoint(
$scope,
Expand Down Expand Up @@ -2714,9 +2719,21 @@ static function (): void {
$scope = $result->getScope();
}
} elseif ($expr instanceof Expr\Closure) {
return $this->processClosureNode($stmt, $expr, $scope, $nodeCallback, $context, null);
$result = $this->processClosureNode($stmt, $expr, $scope, $nodeCallback, $context, null);
return new ExpressionResult(
$result->getScope(),
$result->hasYield(),
[],
[],
);
} elseif ($expr instanceof Expr\ArrowFunction) {
return $this->processArrowFunctionNode($stmt, $expr, $scope, $nodeCallback, null);
$result = $this->processArrowFunctionNode($stmt, $expr, $scope, $nodeCallback, null);
return new ExpressionResult(
$result->getScope(),
$result->hasYield(),
[],
[],
);
} elseif ($expr instanceof ErrorSuppress) {
$result = $this->processExprNode($stmt, $expr->expr, $scope, $nodeCallback, $context);
$hasYield = $result->hasYield();
Expand Down Expand Up @@ -3797,7 +3814,7 @@ private function processClosureNode(
array_merge($statementResult->getImpurePoints(), $closureImpurePoints),
), $closureScope);

return new ExpressionResult($scope, false, [], []);
return new ExpressionResult($scope, false, $statementResult->getThrowPoints(), $statementResult->getImpurePoints());
}

$count = 0;
Expand Down Expand Up @@ -3831,7 +3848,7 @@ private function processClosureNode(
array_merge($statementResult->getImpurePoints(), $closureImpurePoints),
), $closureScope);

return new ExpressionResult($scope->processClosureScope($closureScope, null, $byRefUses), false, [], []);
return new ExpressionResult($scope->processClosureScope($closureScope, null, $byRefUses), false, $statementResult->getThrowPoints(), $statementResult->getImpurePoints());
}

/**
Expand Down Expand Up @@ -3864,9 +3881,9 @@ private function processArrowFunctionNode(
throw new ShouldNotHappenException();
}
$nodeCallback(new InArrowFunctionNode($arrowFunctionType, $expr), $arrowFunctionScope);
$this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $nodeCallback, ExpressionContext::createTopLevel());
$exprResult = $this->processExprNode($stmt, $expr->expr, $arrowFunctionScope, $nodeCallback, ExpressionContext::createTopLevel());

return new ExpressionResult($scope, false, [], []);
return new ExpressionResult($scope, false, $exprResult->getThrowPoints(), $exprResult->getImpurePoints());
}

/**
Expand Down Expand Up @@ -4052,11 +4069,23 @@ private function processArgs(
if ($arg->value instanceof Expr\Closure) {
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context);
$result = $this->processClosureNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context, $parameterType ?? null);
if ($calleeReflection instanceof FunctionReflection) {
// immediately called
$throwPoints = array_merge($throwPoints, array_map(static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $arg->value), $result->getThrowPoints()));
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
}
} elseif ($arg->value instanceof Expr\ArrowFunction) {
$this->callNodeCallbackWithExpression($nodeCallback, $arg->value, $scopeToPass, $context);
$result = $this->processArrowFunctionNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $parameterType ?? null);
if ($calleeReflection instanceof FunctionReflection) {
// immediately called
$throwPoints = array_merge($throwPoints, array_map(static fn (ThrowPoint $throwPoint) => $throwPoint->isExplicit() ? ThrowPoint::createExplicit($scope, $throwPoint->getType(), $arg->value, $throwPoint->canContainAnyThrowable()) : ThrowPoint::createImplicit($scope, $arg->value), $result->getThrowPoints()));
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
}
} else {
$result = $this->processExprNode($stmt, $arg->value, $scopeToPass, $nodeCallback, $context->enterDeep());
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
}
$scope = $result->getScope();
if ($assignByReference) {
Expand All @@ -4070,8 +4099,6 @@ private function processArgs(
}

$hasYield = $hasYield || $result->hasYield();
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
if ($i !== 0 || $closureBindScope === null) {
continue;
}
Expand Down
2 changes: 1 addition & 1 deletion src/PhpDoc/TypeNodeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -933,7 +933,7 @@ function (CallableTypeParameterNode $parameterNode) use ($nameScope, &$isVariadi
$mainType instanceof ObjectType
&& $mainType->getClassName() === Closure::class
) {
return new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags);
return new ClosureType($parameters, $returnType, $isVariadic, $templateTypeMap, null, null, $templateTags, []);
}

return new ErrorType();
Expand Down
5 changes: 5 additions & 0 deletions src/Reflection/Callables/CallableParametersAcceptor.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,9 @@
interface CallableParametersAcceptor extends ParametersAcceptor
{

/**
* @return SimpleThrowPoint[]
*/
public function getThrowPoints(): array;

}
73 changes: 67 additions & 6 deletions src/Reflection/Callables/FunctionCallableVariant.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,38 @@

namespace PHPStan\Reflection\Callables;

use PHPStan\Reflection\ParametersAcceptor;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParameterReflectionWithPhpDocs;
use PHPStan\Reflection\ParametersAcceptorWithPhpDocs;
use PHPStan\Type\Generic\TemplateTypeMap;
use PHPStan\Type\Generic\TemplateTypeVarianceMap;
use PHPStan\Type\NeverType;
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
use Throwable;
use function array_map;

class FunctionCallableVariant implements CallableParametersAcceptor
class FunctionCallableVariant implements CallableParametersAcceptor, ParametersAcceptorWithPhpDocs
{

/** @var SimpleThrowPoint[]|null */
private ?array $throwPoints = null;

public function __construct(
private ParametersAcceptor $variant,
private FunctionReflection|ExtendedMethodReflection $function,
private ParametersAcceptorWithPhpDocs $variant,
)
{
}

/**
* @param ParametersAcceptor[] $variants
* @param ParametersAcceptorWithPhpDocs[] $variants
* @return self[]
*/
public static function createFromVariants(array $variants): array
public static function createFromVariants(FunctionReflection|ExtendedMethodReflection $function, array $variants): array
{
return array_map(static fn (ParametersAcceptor $variant) => new self($variant), $variants);
return array_map(static fn (ParametersAcceptorWithPhpDocs $variant) => new self($function, $variant), $variants);
}

public function getTemplateTypeMap(): TemplateTypeMap
Expand All @@ -35,6 +46,9 @@ public function getResolvedTemplateTypeMap(): TemplateTypeMap
return $this->variant->getResolvedTemplateTypeMap();
}

/**
* @return array<int, ParameterReflectionWithPhpDocs>
*/
public function getParameters(): array
{
return $this->variant->getParameters();
Expand All @@ -50,4 +64,51 @@ public function getReturnType(): Type
return $this->variant->getReturnType();
}

public function getPhpDocReturnType(): Type
{
return $this->variant->getPhpDocReturnType();
}

public function getNativeReturnType(): Type
{
return $this->variant->getNativeReturnType();
}

public function getCallSiteVarianceMap(): TemplateTypeVarianceMap
{
return $this->variant->getCallSiteVarianceMap();
}

public function getThrowPoints(): array
{
if ($this->throwPoints !== null) {
return $this->throwPoints;
}

if ($this->variant instanceof CallableParametersAcceptor) {
return $this->throwPoints = $this->variant->getThrowPoints();
}

$returnType = $this->variant->getReturnType();
$throwType = $this->function->getThrowType();
if ($throwType === null) {
if ($returnType instanceof NeverType && $returnType->isExplicit()) {
$throwType = new ObjectType(Throwable::class);
}
}

$throwPoints = [];
if ($throwType !== null) {
if (!$throwType->isVoid()->yes()) {
$throwPoints[] = SimpleThrowPoint::createExplicit($throwType, true);
}
} else {
if (!(new ObjectType(Throwable::class))->isSuperTypeOf($returnType)->yes()) {
$throwPoints[] = SimpleThrowPoint::createImplicit();
}
}

return $this->throwPoints = $throwPoints;
}

}

0 comments on commit 74c02de

Please sign in to comment.