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
2 changes: 2 additions & 0 deletions src/Analyser/ExprHandler/ArrayDimFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
use PHPStan\Analyser\IssetabilityDescriptor;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\NoopNodeCallback;
Expand Down Expand Up @@ -125,6 +126,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
throwPoints: $throwPoints,
impurePoints: $impurePoints,
containsNullsafe: $varResult->containsNullsafe(),
issetabilityDescriptor: IssetabilityDescriptor::offset($varResult, $dimResult),
);
}

Expand Down
19 changes: 19 additions & 0 deletions src/Analyser/ExprHandler/AssignOpHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\ImplicitToStringCallHelper;
use PHPStan\Analyser\ExprHandler\Helper\NonNullabilityHelper;
use PHPStan\Analyser\InternalThrowPoint;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\NoopNodeCallback;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\CoalesceExpressionNode;
use PHPStan\Reflection\InitializerExprTypeResolver;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Constant\ConstantIntegerType;
Expand All @@ -45,6 +48,7 @@ public function __construct(
private InitializerExprTypeResolver $initializerExprTypeResolver,
private ImplicitToStringCallHelper $implicitToStringCallHelper,
private ExpressionResultFactory $expressionResultFactory,
private NonNullabilityHelper $nonNullabilityHelper,
)
{
}
Expand All @@ -57,6 +61,17 @@ public function supports(Expr $expr): bool
public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult
{
$beforeScope = $scope;
$condResult = null;
if ($expr instanceof Expr\AssignOp\Coalesce) {
// `$lvalue ??= ...` is `$lvalue = $lvalue ?? ...`: price the left side
// once as a read (mirroring CoalesceHandler's left-side processing) so
// its result carries the isset descriptor for NullCoalesceRule. The
// NoopNodeCallback avoids duplicate reports and the read's scope is
// discarded: processAssignVar() walks the target itself.
$nonNullabilityResult = $this->nonNullabilityHelper->ensureNonNullability($scope, $expr->var);
$condScope = $nodeScopeResolver->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->var);
$condResult = $nodeScopeResolver->processExprNode($stmt, $expr->var, $condScope, $storage, new NoopNodeCallback(), $context->enterDeep());
}
$assignResult = $this->assignHandler->processAssignVar(
$nodeScopeResolver,
$scope,
Expand Down Expand Up @@ -114,6 +129,10 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
$impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints());
}

if ($expr instanceof Expr\AssignOp\Coalesce) {
$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new CoalesceExpressionNode($expr, $condResult, 'on left side of ??='), $beforeScope, $storage, $context);
}

return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/CoalesceHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\CoalesceExpressionNode;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\NeverType;
Expand Down Expand Up @@ -135,6 +136,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scope = $scope->filterByTruthyValue(new Expr\Isset_([$expr->left]))->mergeWith($rightResult->getScope());
}

$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new CoalesceExpressionNode($expr, $condResult, 'on left side of ??'), $beforeScope, $storage, $context);

return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/EmptyHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\EmptyExpressionNode;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\BooleanType;
use PHPStan\Type\Constant\ConstantBooleanType;
Expand Down Expand Up @@ -95,6 +96,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scope = $this->nonNullabilityHelper->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions());
$scope = $nodeScopeResolver->lookForUnsetAllowedUndefinedExpressions($scope, $expr->expr);

$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new EmptyExpressionNode($expr, $exprResult), $beforeScope, $storage, $context);

return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
Expand Down
5 changes: 5 additions & 0 deletions src/Analyser/ExprHandler/IssetHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Node\Expr\TypeExpr;
use PHPStan\Node\IssetExpr;
use PHPStan\Node\IssetExpressionNode;
use PHPStan\Rules\Arrays\AllowedArrayKeysTypes;
use PHPStan\ShouldNotHappenException;
use PHPStan\Type\Accessory\HasOffsetType;
Expand Down Expand Up @@ -352,10 +353,12 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$impurePoints = [];
$nonNullabilityResults = [];
$isAlwaysTerminating = false;
$varResults = [];
foreach ($expr->vars as $var) {
$nonNullabilityResult = $this->nonNullabilityHelper->ensureNonNullability($scope, $var);
$scope = $nodeScopeResolver->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var);
$varResult = $nodeScopeResolver->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, $context->enterDeep());
$varResults[] = $varResult;
$scope = $varResult->getScope();
$hasYield = $hasYield || $varResult->hasYield();
$throwPoints = array_merge($throwPoints, $varResult->getThrowPoints());
Expand Down Expand Up @@ -388,6 +391,8 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$scope = $this->nonNullabilityHelper->revertNonNullability($scope, $nonNullabilityResult->getSpecifiedExpressions());
}

$nodeScopeResolver->callNodeCallbackWithExpression($nodeCallback, new IssetExpressionNode($expr, $varResults), $beforeScope, $storage, $context);

return $this->expressionResultFactory->create(
$scope,
beforeScope: $beforeScope,
Expand Down
3 changes: 3 additions & 0 deletions src/Analyser/ExprHandler/PropertyFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
use PHPStan\Analyser\InternalThrowPoint;
use PHPStan\Analyser\IssetabilityDescriptor;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
Expand All @@ -22,6 +23,7 @@
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Php\PhpVersion;
use PHPStan\Rules\Properties\FoundPropertyReflection;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
use PHPStan\Type\ErrorType;
use PHPStan\Type\MixedType;
Expand Down Expand Up @@ -95,6 +97,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
throwPoints: $throwPoints,
impurePoints: $impurePoints,
containsNullsafe: $varResult->containsNullsafe(),
issetabilityDescriptor: IssetabilityDescriptor::property($varResult, fn (MutatingScope $s): ?FoundPropertyReflection => $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $s), $expr),
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/Analyser/ExprHandler/StaticPropertyFetchHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ExprHandler\Helper\NullsafeShortCircuitingHelper;
use PHPStan\Analyser\ImpurePoint;
use PHPStan\Analyser\IssetabilityDescriptor;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
use PHPStan\Analyser\SpecifiedTypes;
use PHPStan\Analyser\TypeSpecifier;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Rules\Properties\FoundPropertyReflection;
use PHPStan\Rules\Properties\PropertyReflectionFinder;
use PHPStan\Type\ErrorType;
use PHPStan\Type\MixedType;
Expand Down Expand Up @@ -67,6 +69,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
];
$isAlwaysTerminating = false;
$containsNullsafe = false;
$classResult = null;
if ($expr->class instanceof Expr) {
$classResult = $nodeScopeResolver->processExprNode($stmt, $expr->class, $scope, $storage, $nodeCallback, $context->enterDeep());
$hasYield = $classResult->hasYield();
Expand Down Expand Up @@ -94,6 +97,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
throwPoints: $throwPoints,
impurePoints: $impurePoints,
containsNullsafe: $containsNullsafe,
issetabilityDescriptor: IssetabilityDescriptor::property($classResult, fn (MutatingScope $s): ?FoundPropertyReflection => $this->propertyReflectionFinder->findPropertyReflectionFromNode($expr, $s), $expr),
);
}

Expand Down
2 changes: 2 additions & 0 deletions src/Analyser/ExprHandler/VariableHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Analyser\ExpressionResultStorage;
use PHPStan\Analyser\ExprHandler;
use PHPStan\Analyser\ImpurePoint;
use PHPStan\Analyser\IssetabilityDescriptor;
use PHPStan\Analyser\MutatingScope;
use PHPStan\Analyser\NodeScopeResolver;
use PHPStan\Analyser\Scope;
Expand Down Expand Up @@ -103,6 +104,7 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
$isAlwaysTerminating,
$throwPoints,
$impurePoints,
issetabilityDescriptor: is_string($expr->name) ? IssetabilityDescriptor::variable($expr->name) : null,
truthyScopeCallback: static fn (): MutatingScope => $scope->filterByTruthyValue($expr),
falseyScopeCallback: static fn (): MutatingScope => $scope->filterByFalseyValue($expr),
);
Expand Down
27 changes: 27 additions & 0 deletions src/Analyser/ExpressionResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public function __construct(
private array $throwPoints,
private array $impurePoints,
private bool $containsNullsafe = false,
private ?IssetabilityDescriptor $issetabilityDescriptor = null,
?callable $truthyScopeCallback = null,
?callable $falseyScopeCallback = null,
)
Expand Down Expand Up @@ -69,6 +70,32 @@ public function containsNullsafe(): bool
return $this->containsNullsafe;
}

/**
* The isset/empty/?? view of this expression evaluated at the given
* scope: folds the chain descriptor, or builds a leaf resolution from the
* expression's own type when it is not a chain link (e.g. a method-call-rooted
* base like $this->getFoo()['x']). $useNativeTypes selects native vs phpdoc.
*/
public function getIssetabilityResolution(MutatingScope $scope, bool $useNativeTypes): IssetabilityResolution
{
if ($this->issetabilityDescriptor !== null) {
return $this->issetabilityDescriptor->resolve($scope, $useNativeTypes, $this->expr);
}

$type = $this->getTypeOnScope($scope, $useNativeTypes);

return new IssetabilityResolution(
IssetabilityLinkInfo::leaf($type, $this->expr, $this->expr instanceof Expr\NullsafePropertyFetch),
null,
);
}

/** Prices this result's expression on the given scope in the requested flavour. */
public function getTypeOnScope(MutatingScope $scope, bool $useNativeTypes): Type
{
return $useNativeTypes ? $scope->getNativeType($this->expr) : $scope->getType($this->expr);
}

/**
* @return InternalThrowPoint[]
*/
Expand Down
1 change: 1 addition & 0 deletions src/Analyser/ExpressionResultFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function create(
array $throwPoints,
array $impurePoints,
bool $containsNullsafe = false,
?IssetabilityDescriptor $issetabilityDescriptor = null,
?callable $truthyScopeCallback = null,
?callable $falseyScopeCallback = null,
): ExpressionResult;
Expand Down
Loading
Loading