Merge remote-tracking branch 'felixfbecker/master' into return-self
						commit
						93df21d132
					
				| 
						 | 
					@ -0,0 +1,37 @@
 | 
				
			||||||
 | 
					<?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 ($bar->test() as $value) {
 | 
				
			||||||
 | 
					    $
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					@ -1086,6 +1103,27 @@ class DefinitionResolver
 | 
				
			||||||
            return new Types\Mixed_;
 | 
					            return new Types\Mixed_;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // FOREACH KEY/VARIABLE
 | 
				
			||||||
 | 
					        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\Mixed_();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // FOREACH VALUE/VARIABLE
 | 
				
			||||||
 | 
					        if ($node instanceof Node\ForeachValue
 | 
				
			||||||
 | 
					            || ($node instanceof Node\Expression\Variable && $node->parent instanceof Node\ForeachValue)
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            $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 (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -554,6 +554,146 @@ 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 function call' => [
 | 
				
			||||||
 | 
					                new Position(35, 5),
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    new CompletionItem(
 | 
				
			||||||
 | 
					                        '$value',
 | 
				
			||||||
 | 
					                        CompletionItemKind::VARIABLE,
 | 
				
			||||||
 | 
					                        '\\Foo\\Bar',
 | 
				
			||||||
 | 
					                        null,
 | 
				
			||||||
 | 
					                        null,
 | 
				
			||||||
 | 
					                        null,
 | 
				
			||||||
 | 
					                        null,
 | 
				
			||||||
 | 
					                        new TextEdit(new Range(new Position(35, 5), new Position(35, 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');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,7 +36,7 @@
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "containerName": "A"
 | 
					                "containerName": "A"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            "type__tostring": "string[]",
 | 
					            "type__tostring": "bool[]",
 | 
				
			||||||
            "type": {},
 | 
					            "type": {},
 | 
				
			||||||
            "declarationLine": "protected $foo;",
 | 
					            "declarationLine": "protected $foo;",
 | 
				
			||||||
            "documentation": null,
 | 
					            "documentation": null,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "containerName": "A"
 | 
					                "containerName": "A"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            "type__tostring": "\\__CLASS__[]",
 | 
					            "type__tostring": "bool[]",
 | 
				
			||||||
            "type": {},
 | 
					            "type": {},
 | 
				
			||||||
            "declarationLine": "private static $deprecationsTriggered;",
 | 
					            "declarationLine": "private static $deprecationsTriggered;",
 | 
				
			||||||
            "documentation": null,
 | 
					            "documentation": null,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue