Skip to content

Commit 20960a8

Browse files
authored
fix(DefinitionResolver): find variables in sibling children (#568)
Fixes #566
1 parent 8439da9 commit 20960a8

File tree

2 files changed

+55
-24
lines changed

2 files changed

+55
-24
lines changed

fixtures/completion/foreach.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ public function test() { }
3838

3939
foreach ($unknownArray as $member->access => $unknown) {
4040
$unkno
41+
42+
foreach ($loop as $loop) {
43+
}
44+
45+
foreach ($loop->getArray() as $loop) {
46+
}

src/DefinitionResolver.php

Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,15 @@ public function resolveVariableToNode($var)
545545
} else {
546546
throw new \InvalidArgumentException('$var must be Variable, Param or ClosureUse, not ' . get_class($var));
547547
}
548+
if (empty($name)) {
549+
return null;
550+
}
551+
552+
$shouldDescend = function ($nodeToDescand) {
553+
// Make sure not to decend into functions or classes (they represent a scope boundary)
554+
return !($nodeToDescand instanceof PhpParser\FunctionLike || $nodeToDescand instanceof PhpParser\ClassLike);
555+
};
556+
548557
// Traverse the AST up
549558
do {
550559
// If a function is met, check the parameters and use statements
@@ -569,39 +578,55 @@ public function resolveVariableToNode($var)
569578
break;
570579
}
571580

572-
// If we get to a ForeachStatement, check the keys and values
573-
if ($n instanceof Node\Statement\ForeachStatement) {
574-
if ($n->foreachKey instanceof Node\Expression\Variable
575-
&& $n->foreachKey->expression->getName() === $name
576-
) {
577-
return $n->foreachKey;
578-
}
579-
if ($n->foreachValue
580-
&& $n->foreachValue->expression instanceof Node\Expression\Variable
581-
&& $n->foreachValue->expression->getName() === $name
582-
) {
583-
return $n->foreachValue;
584-
}
585-
}
586-
587-
// Check each previous sibling node for a variable assignment to that variable
581+
// Check each previous sibling node and their descendents for a variable assignment to that variable
582+
// Each previous sibling could contain a declaration of the variable
588583
while (($prevSibling = $n->getPreviousSibling()) !== null && $n = $prevSibling) {
589-
if ($n instanceof Node\Statement\ExpressionStatement) {
590-
$n = $n->expression;
591-
}
592-
if (
593-
// TODO - clean this up
594-
($n instanceof Node\Expression\AssignmentExpression && $n->operator->kind === PhpParser\TokenKind::EqualsToken)
595-
&& $n->leftOperand instanceof Node\Expression\Variable && $n->leftOperand->getName() === $name
596-
) {
584+
585+
// Check the sibling itself
586+
if (self::isVariableDeclaration($n, $name)) {
597587
return $n;
598588
}
589+
590+
// Check descendant of this sibling (e.g. the children of a previous if block)
591+
foreach ($n->getDescendantNodes($shouldDescend) as $descendant) {
592+
if (self::isVariableDeclaration($descendant, $name)) {
593+
return $descendant;
594+
}
595+
}
599596
}
600597
} while (isset($n) && $n = $n->parent);
601598
// Return null if nothing was found
602599
return null;
603600
}
604601

602+
/**
603+
* Checks whether the given Node declares the given variable name
604+
*
605+
* @param Node $n The Node to check
606+
* @param string $name The name of the wanted variable
607+
* @return bool
608+
*/
609+
private static function isVariableDeclaration(Node $n, string $name)
610+
{
611+
if (
612+
// TODO - clean this up
613+
($n instanceof Node\Expression\AssignmentExpression && $n->operator->kind === PhpParser\TokenKind::EqualsToken)
614+
&& $n->leftOperand instanceof Node\Expression\Variable && $n->leftOperand->getName() === $name
615+
) {
616+
return true;
617+
}
618+
619+
if (
620+
($n instanceof Node\ForeachValue || $n instanceof Node\ForeachKey)
621+
&& $n->expression instanceof Node\Expression\Variable
622+
&& $n->expression->getName() === $name
623+
) {
624+
return true;
625+
}
626+
627+
return false;
628+
}
629+
605630
/**
606631
* Given an expression node, resolves that expression recursively to a type.
607632
* If the type could not be resolved, returns Types\Mixed_.

0 commit comments

Comments
 (0)