Skip to content
Merged
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
61 changes: 61 additions & 0 deletions src/Analyser/ArgsResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php declare(strict_types = 1);

namespace PHPStan\Analyser;

use PHPStan\Reflection\ParametersAcceptor;

/**
* Result of NodeScopeResolver::processArgs(): the scope/throw/impure state after
* processing all arguments (wrapped ExpressionResult) plus the ParametersAcceptor
* resolved from the arg types gathered on the arg-to-arg evolving scope. The
* resolved acceptor is type-driven (selectFromTypes) so its generics are resolved
* against the actual argument types - callers wire it into the call's return
* type. Null when the call had no variants (dynamic callee).
*/
final class ArgsResult
{

public function __construct(
private ExpressionResult $expressionResult,
private ?ParametersAcceptor $resolvedParametersAcceptor,
)
{
}

public function getScope(): MutatingScope
{
return $this->expressionResult->getScope();
}

public function hasYield(): bool
{
return $this->expressionResult->hasYield();
}

public function isAlwaysTerminating(): bool
{
return $this->expressionResult->isAlwaysTerminating();
}

/**
* @return InternalThrowPoint[]
*/
public function getThrowPoints(): array
{
return $this->expressionResult->getThrowPoints();
}

/**
* @return ImpurePoint[]
*/
public function getImpurePoints(): array
{
return $this->expressionResult->getImpurePoints();
}

public function getResolvedParametersAcceptor(): ?ParametersAcceptor
{
return $this->resolvedParametersAcceptor;
}

}
49 changes: 32 additions & 17 deletions src/Analyser/ExprHandler/FuncCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
{
$beforeScope = $scope;
$parametersAcceptor = null;
$variants = [];
$namedArgumentsVariants = null;
$functionReflection = null;
$throwPoints = [];
$impurePoints = [];
Expand All @@ -130,12 +132,11 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$nameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep());
$nameType = $nameResult->getType();
if (!$nameType->isCallable()->no()) {
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$expr->getArgs(),
$nameType->getCallableParametersAcceptors($scope),
null,
);
$variants = $nameType->getCallableParametersAcceptors($scope);
// A structural acceptor (names/positions/variadic) drives the per-arg
// metadata and the throw/impure points - generics are resolved
// type-driven by processArgs() into $resolvedParametersAcceptor.
$parametersAcceptor = ParametersAcceptorSelector::combineVariantsForNormalization($expr->getArgs(), $variants, null);
}

$scope = $nameResult->getScope();
Expand All @@ -160,16 +161,12 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
}
} elseif ($this->reflectionProvider->hasFunction($expr->name, $scope)) {
$functionReflection = $this->reflectionProvider->getFunction($expr->name, $scope);
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$expr->getArgs(),
$functionReflection->getVariants(),
$functionReflection->getNamedArgumentsVariants(),
);
$impurePoint = SimpleImpurePoint::createFromVariant($functionReflection, $parametersAcceptor, $scope, $expr->getArgs());
if ($impurePoint !== null) {
$impurePoints[] = new ImpurePoint($scope, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain());
}
$variants = $functionReflection->getVariants();
$namedArgumentsVariants = $functionReflection->getNamedArgumentsVariants();
// A structural acceptor (names/positions/variadic) drives argument
// normalization, the impure point and the throw points - generics are
// resolved type-driven by processArgs() into $resolvedParametersAcceptor.
$parametersAcceptor = ParametersAcceptorSelector::combineVariantsForNormalization($expr->getArgs(), $variants, $namedArgumentsVariants);
} else {
$impurePoints[] = new ImpurePoint(
$scope,
Expand Down Expand Up @@ -291,13 +288,24 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
}

$scopeBeforeArgs = $scope;
$argsResult = $nodeScopeResolver->processArgs($stmt, $functionReflection, null, $parametersAcceptor, $normalizedExpr, $scope, $storage, $nodeCallbackForArgs, $context);
$argsResult = $nodeScopeResolver->processArgs($stmt, $functionReflection, null, $variants, $namedArgumentsVariants, $normalizedExpr, $scope, $storage, $nodeCallbackForArgs, $context);
$resolvedParametersAcceptor = $argsResult->getResolvedParametersAcceptor();
$scope = $argsResult->getScope();
$hasYield = $argsResult->hasYield();
$throwPoints = array_merge($throwPoints, $argsResult->getThrowPoints());
$impurePoints = array_merge($impurePoints, $argsResult->getImpurePoints());
$isAlwaysTerminating = $isAlwaysTerminating || $argsResult->isAlwaysTerminating();

if ($functionReflection !== null) {
// created after the args were processed - the side-effect flip
// parameters (print_r's $return, ...) read an argument's type, which
// is only available once the argument was processed
$impurePoint = SimpleImpurePoint::createFromVariant($functionReflection, $parametersAcceptor, $scope, $expr->getArgs());
if ($impurePoint !== null) {
$impurePoints[] = new ImpurePoint($scopeBeforeArgs, $expr, $impurePoint->getIdentifier(), $impurePoint->getDescription(), $impurePoint->isCertain());
}
}

if ($arrayWalkValueTypes !== null && $arrayWalkArrayArg !== null) {
$arrayWalkValueType = $arrayWalkValueTypes[0];
$arrayWalkValueNativeType = $arrayWalkValueTypes[1];
Expand Down Expand Up @@ -336,6 +344,13 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
}

if ($functionReflection !== null) {
// A conditional-return never (e.g. `($x is Foo ? never : string)`) only
// resolves to never once the actual argument types are folded in by the
// type-driven resolved acceptor.
if ($resolvedParametersAcceptor !== null) {
$resolvedReturnType = $resolvedParametersAcceptor->getReturnType();
$isAlwaysTerminating = $isAlwaysTerminating || ($resolvedReturnType instanceof NeverType && $resolvedReturnType->isExplicit());
}
$functionThrowPoint = $this->getFunctionThrowPoint($functionReflection, $parametersAcceptor, $normalizedExpr, $scope, $context);
if ($functionThrowPoint !== null) {
$throwPoints[] = $functionThrowPoint;
Expand Down
39 changes: 28 additions & 11 deletions src/Analyser/ExprHandler/MethodCallHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,20 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scope = $scope->restoreOriginalScopeAfterClosureBind($originalScope);
}
$parametersAcceptor = null;
$variants = [];
$namedArgumentsVariants = null;
$methodReflection = null;
$calledOnType = $varResult->getType();
if ($expr->name instanceof Identifier) {
$methodName = $expr->name->name;
$methodReflection = $scope->getMethodReflection($calledOnType, $methodName);
if ($methodReflection !== null) {
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$expr->getArgs(),
$methodReflection->getVariants(),
$methodReflection->getNamedArgumentsVariants(),
);

$variants = $methodReflection->getVariants();
$namedArgumentsVariants = $methodReflection->getNamedArgumentsVariants();
// A structural acceptor (names/positions/variadic) drives argument
// normalization, the impure point and the throw point - generics are
// resolved type-driven by processArgs() into $resolvedParametersAcceptor.
$parametersAcceptor = ParametersAcceptorSelector::combineVariantsForNormalization($expr->getArgs(), $variants, $namedArgumentsVariants);
}
} else {
$methodNameResult = $nodeScopeResolver->processExprNode($stmt, $expr->name, $scope, $storage, $nodeCallback, $context->enterDeep());
Expand Down Expand Up @@ -145,16 +146,26 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$stmt,
$methodReflection,
$methodReflection !== null ? $scope->getNakedMethod($calledOnType, $methodReflection->getName()) : null,
$parametersAcceptor,
$variants,
$namedArgumentsVariants,
$normalizedExpr,
$scope,
$storage,
$nodeCallback,
$context,
);
$resolvedParametersAcceptor = $argsResult->getResolvedParametersAcceptor();
$scope = $argsResult->getScope();

if ($methodReflection !== null) {
// The early structural check above only sees the unresolved acceptor
// return type; a conditional-return never (e.g. `($x is Foo ? never :
// string)`) only resolves to never once the actual argument types are
// folded in by the type-driven resolved acceptor.
if ($resolvedParametersAcceptor !== null) {
$resolvedReturnType = $resolvedParametersAcceptor->getReturnType();
$isAlwaysTerminating = $isAlwaysTerminating || ($resolvedReturnType instanceof NeverType && $resolvedReturnType->isExplicit());
}
$methodThrowPoint = $this->methodThrowPointHelper->getThrowPoint($methodReflection, $parametersAcceptor, $normalizedExpr, $scope, $context);
if ($methodThrowPoint !== null) {
$throwPoints[] = $methodThrowPoint;
Expand All @@ -164,21 +175,27 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$nodeScopeResolver->callNodeCallback($nodeCallback, new InvalidateExprNode($normalizedExpr->var), $scope, $storage);
$scope = $scope->invalidateExpression($normalizedExpr->var, true, $methodReflection->getDeclaringClass());
} elseif ($this->rememberPossiblyImpureFunctionValues && $methodReflection->hasSideEffects()->maybe() && !$methodReflection->getDeclaringClass()->isBuiltin()) {
// the remembered call value and the @phpstan-self-out type are
// generic-sensitive: resolve them from the type-driven acceptor
// processArgs() selected (generics resolved against the actual arg
// types), falling back to the structural acceptor for dynamic callees.
$acceptorForGenerics = $resolvedParametersAcceptor ?? $parametersAcceptor;
$scope = $scope->assignExpression(
new PossiblyImpureCallExpr($normalizedExpr, $normalizedExpr->var, sprintf('%s::%s()', $methodReflection->getDeclaringClass()->getDisplayName(), $methodReflection->getName())),
$parametersAcceptor->getReturnType(),
$acceptorForGenerics->getReturnType(),
new MixedType(),
);
}
if (!$methodReflection->isStatic()) {
$selfOutType = $methodReflection->getSelfOutType();
if ($selfOutType !== null) {
$acceptorForGenerics = $resolvedParametersAcceptor ?? $parametersAcceptor;
$scope = $scope->assignExpression(
$normalizedExpr->var,
TemplateTypeHelper::resolveTemplateTypes(
$selfOutType,
$parametersAcceptor->getResolvedTemplateTypeMap(),
$parametersAcceptor instanceof ExtendedParametersAcceptor ? $parametersAcceptor->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
$acceptorForGenerics->getResolvedTemplateTypeMap(),
$acceptorForGenerics instanceof ExtendedParametersAcceptor ? $acceptorForGenerics->getCallSiteVarianceMap() : TemplateTypeVarianceMap::createEmpty(),
TemplateTypeVariance::createCovariant(),
),
$scope->getNativeType($normalizedExpr->var),
Expand Down
27 changes: 13 additions & 14 deletions src/Analyser/ExprHandler/NewHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use PHPStan\Reflection\Callables\SimpleImpurePoint;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\Dummy\DummyConstructorReflection;
use PHPStan\Reflection\ExtendedMethodReflection;
use PHPStan\Reflection\ExtendedParametersAcceptor;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\ParametersAcceptor;
Expand Down Expand Up @@ -122,12 +123,10 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$classReflection = $this->reflectionProvider->getAnonymousClassReflection($expr->class, $scope); // populates $expr->class->name
if ($classReflection->hasConstructor()) {
$constructorReflection = $classReflection->getConstructor();
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$expr->getArgs(),
$constructorReflection->getVariants(),
$constructorReflection->getNamedArgumentsVariants(),
);
// A structural acceptor (names/positions/variadic) drives argument
// normalization and the throw point - generics are resolved
// type-driven by processArgs() into the resolved acceptor.
$parametersAcceptor = ParametersAcceptorSelector::combineVariantsForNormalization($expr->getArgs(), $constructorReflection->getVariants(), $constructorReflection->getNamedArgumentsVariants());

if ($constructorReflection->getDeclaringClass()->getName() === $classReflection->getName()) {
$constructorResult = null;
Expand Down Expand Up @@ -208,7 +207,9 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
}
}

$argsResult = $nodeScopeResolver->processArgs($stmt, $constructorReflection, null, $parametersAcceptor, $normalizedExpr, $scope, $storage, $nodeCallback, $context);
$variants = $constructorReflection !== null ? $constructorReflection->getVariants() : [];
$namedArgumentsVariants = $constructorReflection !== null ? $constructorReflection->getNamedArgumentsVariants() : null;
$argsResult = $nodeScopeResolver->processArgs($stmt, $constructorReflection, null, $variants, $namedArgumentsVariants, $normalizedExpr, $scope, $storage, $nodeCallback, $context);
$scope = $argsResult->getScope();
$hasYield = $hasYield || $argsResult->hasYield();
$throwPoints = array_merge($throwPoints, $argsResult->getThrowPoints());
Expand Down Expand Up @@ -245,7 +246,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
}

/**
* @return array{?MethodReflection, ?ClassReflection, ?ParametersAcceptor, ImpurePoint[]}
* @return array{?ExtendedMethodReflection, ?ClassReflection, ?ParametersAcceptor, ImpurePoint[]}
*/
private function processConstructorReflection(string $className, New_ $expr, MutatingScope $scope, bool $isDynamic): array
{
Expand All @@ -258,12 +259,10 @@ private function processConstructorReflection(string $className, New_ $expr, Mut
$classReflection = $this->reflectionProvider->getClass($className);
if ($classReflection->hasConstructor()) {
$constructorReflection = $classReflection->getConstructor();
$parametersAcceptor = ParametersAcceptorSelector::selectFromArgs(
$scope,
$expr->getArgs(),
$constructorReflection->getVariants(),
$constructorReflection->getNamedArgumentsVariants(),
);
// A structural acceptor (names/positions/variadic) drives argument
// normalization and the throw point - generics are resolved
// type-driven by processArgs() into the resolved acceptor.
$parametersAcceptor = ParametersAcceptorSelector::combineVariantsForNormalization($expr->getArgs(), $constructorReflection->getVariants(), $constructorReflection->getNamedArgumentsVariants());
}
}

Expand Down
Loading
Loading