private function processNode(\PhpParser\Node $node, Scope $scope, \Closure $nodeCallback)
{
$nodeCallback($node, $scope);
if ($node instanceof \PhpParser\Node\Stmt\ClassLike) {
if ($node instanceof Node\Stmt\Trait_) {
return;
}
if (isset($node->namespacedName)) {
$scope = $scope->enterClass((string) $node->namespacedName);
} else {
$scope = $scope->enterAnonymousClass($this->anonymousClassReflection);
}
} elseif ($node instanceof Node\Stmt\TraitUse) {
$this->processTraitUse($node, $scope, $nodeCallback);
} elseif ($node instanceof \PhpParser\Node\Stmt\Function_) {
$scope = $scope->enterFunction($this->broker->getFunction($node->namespacedName, $scope));
} elseif ($node instanceof \PhpParser\Node\Stmt\ClassMethod) {
$scope = $this->enterClassMethod($scope, $node);
} elseif ($node instanceof \PhpParser\Node\Stmt\Namespace_) {
$scope = $scope->enterNamespace((string) $node->name);
} elseif ($node instanceof \PhpParser\Node\Expr\StaticCall && (is_string($node->class) || $node->class instanceof \PhpParser\Node\Name) && is_string($node->name) && (string) $node->class === 'Closure' && $node->name === 'bind') {
$thisType = null;
if (isset($node->args[1])) {
$argValue = $node->args[1]->value;
if ($argValue instanceof Expr\ConstFetch && (string) $argValue->name === 'null') {
$thisType = null;
} else {
$thisType = $scope->getType($argValue);
}
}
$scopeClass = 'static';
if (isset($node->args[2])) {
$argValue = $node->args[2]->value;
$argValueType = $scope->getType($argValue);
if ($argValueType->getClass() !== null) {
$scopeClass = $argValueType->getClass();
} elseif ($argValue instanceof Expr\ClassConstFetch && $argValue->name === 'class' && $argValue->class instanceof Name) {
$resolvedName = $scope->resolveName($argValue->class);
if ($resolvedName !== null) {
$scopeClass = $resolvedName;
}
} elseif ($argValue instanceof Node\Scalar\String_) {
$scopeClass = $argValue->value;
}
}
$closureBindScope = $scope->enterClosureBind($thisType, $scopeClass);
} elseif ($node instanceof \PhpParser\Node\Expr\Closure) {
$scope = $scope->enterAnonymousFunction($node->params, $node->uses, $node->returnType);
} elseif ($node instanceof Foreach_) {
if ($node->valueVar instanceof Variable) {
$scope = $scope->enterForeach($node->expr, $node->valueVar->name, $node->keyVar !== null && $node->keyVar instanceof Variable ? $node->keyVar->name : null);
} else {
if ($node->keyVar !== null && $node->keyVar instanceof Variable) {
$scope = $scope->assignVariable($node->keyVar->name);
}
if ($node->valueVar instanceof Array_) {
$scope = $this->lookForArrayDestructuringArray($scope, $node->valueVar);
} else {
$scope = $this->lookForAssigns($scope, $node->valueVar);
}
}
} elseif ($node instanceof Catch_) {
if (isset($node->types)) {
$nodeTypes = $node->types;
} elseif (isset($node->type)) {
$nodeTypes = [$node->type];
} else {
throw new \PHPStan\ShouldNotHappenException();
}
$scope = $scope->enterCatch($nodeTypes, $node->var);
} elseif ($node instanceof For_) {
foreach ($node->init as $initExpr) {
$scope = $this->lookForAssigns($scope, $initExpr);
}
foreach ($node->cond as $condExpr) {
$scope = $this->lookForAssigns($scope, $condExpr);
}
foreach ($node->loop as $loopExpr) {
$scope = $this->lookForAssigns($scope, $loopExpr);
}
} elseif ($node instanceof If_) {
$scope = $this->lookForAssigns($scope, $node->cond);
$ifScope = $scope;
$scope = $this->lookForTypeSpecifications($scope, $node->cond);
$this->processNode($node->cond, $scope, $nodeCallback);
$this->processNodes($node->stmts, $scope, $nodeCallback);
foreach ($node->elseifs as $elseif) {
$this->processNode($elseif, $ifScope, $nodeCallback);
$ifScope = $this->lookForAssigns($ifScope, $elseif->cond);
}
if ($node->else !== null) {
$this->processNode($node->else, $ifScope, $nodeCallback);
}
return;
} elseif ($node instanceof Switch_) {
$scope = $this->lookForAssigns($scope, $node->cond);
$switchScope = $scope;
$switchConditionIsTrue = $node->cond instanceof Expr\ConstFetch && strtolower((string) $node->cond->name) === 'true';
foreach ($node->cases as $caseNode) {
if ($caseNode->cond !== null) {
$switchScope = $this->lookForAssigns($switchScope, $caseNode->cond);
if ($switchConditionIsTrue) {
$switchScope = $this->lookForTypeSpecifications($switchScope, $caseNode->cond);
}
}
$this->processNode($caseNode, $switchScope, $nodeCallback);
if ($this->hasEarlyTermination($caseNode->stmts, $switchScope)) {
$switchScope = $scope;
}
}
return;
} elseif ($node instanceof ElseIf_) {
$scope = $this->lookForAssigns($scope, $node->cond);
$scope = $this->lookForTypeSpecifications($scope, $node->cond);
} elseif ($node instanceof While_) {
$scope = $this->lookForAssigns($scope, $node->cond);
$scope = $this->lookForTypeSpecifications($scope, $node->cond);
} elseif ($this->polluteCatchScopeWithTryAssignments && $node instanceof TryCatch) {
foreach ($node->stmts as $statement) {
$scope = $this->lookForAssigns($scope, $statement);
}
} elseif ($node instanceof Ternary) {
$scope = $this->lookForAssigns($scope, $node->cond);
} elseif ($node instanceof Do_) {
foreach ($node->stmts as $statement) {
$scope = $this->lookForAssigns($scope, $statement);
}
} elseif ($node instanceof FuncCall) {
$scope = $scope->enterFunctionCall($node);
} elseif ($node instanceof Expr\StaticCall) {
$scope = $scope->enterFunctionCall($node);
} elseif ($node instanceof MethodCall) {
if ($scope->getType($node->var)->getClass() === 'Closure' && $node->name === 'call' && isset($node->args[0])) {
$closureCallScope = $scope->enterClosureBind($scope->getType($node->args[0]->value), 'static');
}
$scope = $scope->enterFunctionCall($node);
} elseif ($node instanceof Array_) {
foreach ($node->items as $item) {
$scope = $this->lookForAssigns($scope, $item->value);
}
} elseif ($node instanceof New_ && $node->class instanceof Class_) {
$node->args = [];
foreach ($node->class->stmts as $i => $statement) {
if ($statement instanceof Node\Stmt\ClassMethod && $statement->name === '__construct') {
unset($node->class->stmts[$i]);
$node->class->stmts = array_values($node->class->stmts);
break;
}
}
$code = $this->printer->prettyPrint([$node]);
$classReflection = new \ReflectionClass(eval(sprintf('return %s', $code)));
$this->anonymousClassReflection = $this->broker->getClassFromReflection($classReflection);
} elseif ($node instanceof BooleanNot) {
$scope = $scope->enterNegation();
}
$originalScope = $scope;
foreach ($node->getSubNodeNames() as $subNodeName) {
$scope = $originalScope;
$subNode = $node->{$subNodeName};
if (is_array($subNode)) {
$argClosureBindScope = null;
if (isset($closureBindScope) && $subNodeName === 'args') {
$argClosureBindScope = $closureBindScope;
}
$this->processNodes($subNode, $scope, $nodeCallback, $argClosureBindScope);
} elseif ($subNode instanceof \PhpParser\Node) {
if ($node instanceof Coalesce && $subNodeName === 'left') {
$scope = $this->assignVariable($scope, $subNode);
}
if ($node instanceof Ternary && $subNodeName === 'if') {
$scope = $this->lookForTypeSpecifications($scope, $node->cond);
}
if ($node instanceof BooleanAnd && $subNodeName === 'right') {
$scope = $this->lookForTypeSpecifications($scope, $node->left);
}
if (($node instanceof Assign || $node instanceof AssignRef) && $subNodeName === 'var') {
$scope = $this->lookForEnterVariableAssign($scope, $node->var);
}
$nodeScope = $scope;
if ($node instanceof MethodCall && $subNodeName === 'var' && isset($closureCallScope)) {
$nodeScope = $closureCallScope;
}
$this->processNode($subNode, $nodeScope, $nodeCallback);
}
}
}