1
0
Fork 0

WIP foreach completion

pull/551/head
Philip Nelson 2017-12-12 18:49:46 +11:00
parent f46fccd0d3
commit d8fd306ed1
5 changed files with 252 additions and 1 deletions

View File

@ -0,0 +1,41 @@
<?php
namespace Foo;
class Bar {
public $foo;
/** @return Bar[] */
public function test() { }
}
$bar = new Bar();
$bars = $bar->test();
$array1 = [new Bar(), new \stdClass()];
$array2 = ['foo' => $bar, $bar];
$array3 = ['foo' => $bar, 'baz' => $bar];
foreach ($bars as $value) {
$v
$value->
}
foreach ($array1 as $key => $value) {
$
}
foreach ($array2 as $key => $value) {
$
}
foreach ($array3 as $key => $value) {
$
}
foreach ($array1 as list($z, $y)) {
$
}
foreach ($bar->test() as $value) {
$
}

View File

@ -486,6 +486,14 @@ class CompletionProvider
if ($this->isAssignmentToVariableWithPrefix($node, $namePrefix)) { if ($this->isAssignmentToVariableWithPrefix($node, $namePrefix)) {
$vars[] = $node->leftOperand; $vars[] = $node->leftOperand;
} elseif ($node instanceof Node\ForeachKey || $node instanceof Node\ForeachValue) {
foreach ($node->getDescendantNodes() as $descendantNode) {
if ($descendantNode instanceof Node\Expression\Variable
&& ($namePrefix === '' || strpos($descendantNode->getName(), $namePrefix) !== false)
) {
$vars[] = $descendantNode;
}
}
} else { } else {
// Get all descendent variables, then filter to ones that start with $namePrefix. // Get all descendent variables, then filter to ones that start with $namePrefix.
// Avoiding closure usage in tight loop // Avoiding closure usage in tight loop

View File

@ -568,6 +568,20 @@ class DefinitionResolver
} }
break; break;
} }
// If we get to a ForeachStatement, check the keys and values
if ($n instanceof Node\Statement\ForeachStatement) {
if ($n->foreachKey && $n->foreachKey->expression->getName() === $name) {
return $n->foreachKey;
}
if ($n->foreachValue
&& $n->foreachValue->expression instanceof Node\Expression\Variable
&& $n->foreachValue->expression->getName() === $name
) {
return $n->foreachValue;
}
}
// Check each previous sibling node for a variable assignment to that variable // Check each previous sibling node for a variable assignment to that variable
while (($prevSibling = $n->getPreviousSibling()) !== null && $n = $prevSibling) { while (($prevSibling = $n->getPreviousSibling()) !== null && $n = $prevSibling) {
if ($n instanceof Node\Statement\ExpressionStatement) { if ($n instanceof Node\Statement\ExpressionStatement) {
@ -619,6 +633,9 @@ class DefinitionResolver
if ($defNode instanceof Node\Expression\AssignmentExpression || $defNode instanceof Node\UseVariableName) { if ($defNode instanceof Node\Expression\AssignmentExpression || $defNode instanceof Node\UseVariableName) {
return $this->resolveExpressionNodeToType($defNode); return $this->resolveExpressionNodeToType($defNode);
} }
if ($defNode instanceof Node\ForeachKey || $defNode instanceof Node\ForeachValue) {
return $this->getTypeFromNode($defNode);
}
if ($defNode instanceof Node\Parameter) { if ($defNode instanceof Node\Parameter) {
return $this->getTypeFromNode($defNode); return $this->getTypeFromNode($defNode);
} }
@ -900,7 +917,7 @@ class DefinitionResolver
$keyTypes[] = $item->elementKey ? $this->resolveExpressionNodeToType($item->elementKey) : new Types\Integer; $keyTypes[] = $item->elementKey ? $this->resolveExpressionNodeToType($item->elementKey) : new Types\Integer;
} }
} }
$valueTypes = array_unique($keyTypes); $valueTypes = array_unique($valueTypes);
$keyTypes = array_unique($keyTypes); $keyTypes = array_unique($keyTypes);
if (empty($valueTypes)) { if (empty($valueTypes)) {
$valueType = null; $valueType = null;
@ -1080,6 +1097,25 @@ class DefinitionResolver
return new Types\Mixed_; return new Types\Mixed_;
} }
// FOREACH KEY
if ($node instanceof Node\ForeachKey || $node->parent instanceof Node\ForeachKey) {
$foreach = $node->getFirstAncestor(Node\Statement\ForeachStatement::class);
$collectionType = $this->resolveExpressionNodeToType($foreach->forEachCollectionName);
if ($collectionType instanceof Types\Array_) {
return $collectionType->getKeyType();
}
return new Types\Compound([new Types\String_(), new Types\Integer()]);
}
// FOREACH VALUE
if ($node instanceof Node\ForeachValue || $node->getFirstAncestor(Node\ForeachValue::class)) {
$foreach = $node->getFirstAncestor(Node\Statement\ForeachStatement::class);
$collectionType = $this->resolveExpressionNodeToType($foreach->forEachCollectionName);
if ($collectionType instanceof Types\Array_) {
return $collectionType->getValueType();
}
}
// PROPERTIES, CONSTS, CLASS CONSTS, ASSIGNMENT EXPRESSIONS // PROPERTIES, CONSTS, CLASS CONSTS, ASSIGNMENT EXPRESSIONS
// Get the documented type the assignment resolves to. // Get the documented type the assignment resolves to.
if ( if (

View File

@ -337,6 +337,7 @@ class TextDocument
if ($def === null) { if ($def === null) {
return new Hover([], $range); return new Hover([], $range);
} }
$contents = [];
if ($def->declarationLine) { if ($def->declarationLine) {
$contents[] = new MarkedString('php', "<?php\n" . $def->declarationLine); $contents[] = new MarkedString('php', "<?php\n" . $def->declarationLine);
} }

View File

@ -554,6 +554,171 @@ class CompletionTest extends TestCase
], true), $items); ], true), $items);
} }
/**
* @dataProvider foreachProvider
*/
public function testForeach(Position $position, array $expectedItems)
{
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/foreach.php');
$this->loader->open($completionUri, file_get_contents($completionUri));
$items = $this->textDocument->completion(
new TextDocumentIdentifier($completionUri),
$position
)->wait();
$this->assertCompletionsListSubset(new CompletionList($expectedItems, true), $items);
}
public function foreachProvider(): array
{
return [
'foreach value' => [
new Position(18, 6),
[
new CompletionItem(
'$value',
CompletionItemKind::VARIABLE,
'\\Foo\\Bar',
null,
null,
null,
null,
new TextEdit(new Range(new Position(18, 6), new Position(18, 6)), 'alue')
),
]
],
'foreach value resolved' => [
new Position(19, 12),
[
new CompletionItem(
'foo',
CompletionItemKind::PROPERTY,
'mixed'
),
new CompletionItem(
'test',
CompletionItemKind::METHOD,
'\\Foo\\Bar[]'
),
]
],
'array creation with multiple objects' => [
new Position(23, 5),
[
new CompletionItem(
'$value',
CompletionItemKind::VARIABLE,
'\\Foo\\Bar|\\stdClass',
null,
null,
null,
null,
new TextEdit(new Range(new Position(23, 5), new Position(23, 5)), 'value')
),
new CompletionItem(
'$key',
CompletionItemKind::VARIABLE,
'int',
null,
null,
null,
null,
new TextEdit(new Range(new Position(23, 5), new Position(23, 5)), 'key')
),
]
],
'array creation with string/int keys and object values' => [
new Position(27, 5),
[
new CompletionItem(
'$value',
CompletionItemKind::VARIABLE,
'\\Foo\\Bar',
null,
null,
null,
null,
new TextEdit(new Range(new Position(27, 5), new Position(27, 5)), 'value')
),
new CompletionItem(
'$key',
CompletionItemKind::VARIABLE,
'string|int',
null,
null,
null,
null,
new TextEdit(new Range(new Position(27, 5), new Position(27, 5)), 'key')
),
]
],
'array creation with only string keys' => [
new Position(31, 5),
[
new CompletionItem(
'$value',
CompletionItemKind::VARIABLE,
'\\Foo\\Bar',
null,
null,
null,
null,
new TextEdit(new Range(new Position(31, 5), new Position(31, 5)), 'value')
),
new CompletionItem(
'$key',
CompletionItemKind::VARIABLE,
'string',
null,
null,
null,
null,
new TextEdit(new Range(new Position(31, 5), new Position(31, 5)), 'key')
),
]
],
'foreach with list()' => [
new Position(35, 5),
[
new CompletionItem(
'$z',
CompletionItemKind::VARIABLE,
'\\Foo\\Bar|\\stdClass',
null,
null,
null,
null,
new TextEdit(new Range(new Position(35, 5), new Position(35, 5)), 'z')
),
new CompletionItem(
'$y',
CompletionItemKind::VARIABLE,
'\\Foo\\Bar|\\stdClass',
null,
null,
null,
null,
new TextEdit(new Range(new Position(35, 5), new Position(35, 5)), 'y')
),
]
],
'foreach function call' => [
new Position(39, 5),
[
new CompletionItem(
'$value',
CompletionItemKind::VARIABLE,
'\\Foo\\Bar',
null,
null,
null,
null,
new TextEdit(new Range(new Position(39, 5), new Position(39, 5)), 'value')
),
]
],
];
}
public function testMethodReturnType() public function testMethodReturnType()
{ {
$completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/method_return_type.php'); $completionUri = pathToUri(__DIR__ . '/../../../fixtures/completion/method_return_type.php');