diff --git a/src/Analyser/ExprHandler/ShellExecHandler.php b/src/Analyser/ExprHandler/ShellExecHandler.php new file mode 100644 index 0000000000..fa756b8865 --- /dev/null +++ b/src/Analyser/ExprHandler/ShellExecHandler.php @@ -0,0 +1,96 @@ + + */ +#[AutowiredService] +final class ShellExecHandler implements ExprHandler +{ + + public function __construct( + private ImplicitToStringCallHelper $implicitToStringCallHelper, + private ExpressionResultFactory $expressionResultFactory, + ) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof ShellExec; + } + + public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult + { + $beforeScope = $scope; + $hasYield = false; + $throwPoints = []; + $impurePoints = []; + $isAlwaysTerminating = false; + foreach ($expr->parts as $part) { + if (!$part instanceof Expr) { + continue; + } + $partResult = $nodeScopeResolver->processExprNode($stmt, $part, $scope, $storage, $nodeCallback, $context->enterDeep()); + $hasYield = $hasYield || $partResult->hasYield(); + $throwPoints = array_merge($throwPoints, $partResult->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $partResult->getImpurePoints()); + + $toStringResult = $this->implicitToStringCallHelper->processImplicitToStringCall($part, $scope); + $throwPoints = array_merge($throwPoints, $toStringResult->getThrowPoints()); + $impurePoints = array_merge($impurePoints, $toStringResult->getImpurePoints()); + + $isAlwaysTerminating = $isAlwaysTerminating || $partResult->isAlwaysTerminating(); + $scope = $partResult->getScope(); + } + + return $this->expressionResultFactory->create( + $scope, + beforeScope: $beforeScope, + expr: $expr, + hasYield: $hasYield, + isAlwaysTerminating: $isAlwaysTerminating, + throwPoints: $throwPoints, + impurePoints: $impurePoints, + ); + } + + public function resolveType(MutatingScope $scope, Expr $expr): Type + { + return TypeCombinator::union(new StringType(), new ConstantBooleanType(false), new NullType()); + } + + public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes + { + return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); + } + +} diff --git a/src/Analyser/ExprHandler/Virtual/IssetExprHandler.php b/src/Analyser/ExprHandler/Virtual/IssetExprHandler.php new file mode 100644 index 0000000000..8709610e6c --- /dev/null +++ b/src/Analyser/ExprHandler/Virtual/IssetExprHandler.php @@ -0,0 +1,71 @@ + + */ +#[AutowiredService] +final class IssetExprHandler implements ExprHandler +{ + + public function __construct(private ExpressionResultFactory $expressionResultFactory) + { + } + + public function supports(Expr $expr): bool + { + return $expr instanceof IssetExpr; + } + + public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Expr $expr, MutatingScope $scope, ExpressionResultStorage $storage, callable $nodeCallback, ExpressionContext $context): ExpressionResult + { + // a virtual node handler - the caller will only be interested in the + // type; the inner expr is not processed, its type is just reported + + return $this->expressionResultFactory->create( + $scope, + beforeScope: $scope, + expr: $expr, + hasYield: false, + isAlwaysTerminating: false, + throwPoints: [], + impurePoints: [], + ); + } + + public function resolveType(MutatingScope $scope, Expr $expr): Type + { + return $scope->getType($expr->getExpr()); + } + + public function specifyTypes(TypeSpecifier $typeSpecifier, Scope $scope, Expr $expr, TypeSpecifierContext $context): SpecifiedTypes + { + return $typeSpecifier->specifyDefaultTypes($scope, $expr, $context); + } + +}