public function leaveNode(Node $node) { if (!$node instanceof Expr) { return; } $type = null; if ($node instanceof Expr\Assign || $node instanceof Expr\AssignRef) { $type = $node->expr->getAttribute('type'); } elseif ($node instanceof Expr\Ternary) { $exprType1 = $node->if === null ? $node->cond->getAttribute('type') : $node->if->getAttribute('type'); $exprType2 = $node->else->getAttribute('type'); $type = Type::alternatives([$exprType1, $exprType2]); } elseif ($node instanceof Expr\BinaryOp\Coalesce) { $exprType1 = $node->left->getAttribute('type'); $exprType2 = $node->right->getAttribute('type'); $type = Type::alternatives([$exprType1, $exprType2]); } elseif ($node instanceof Expr\ErrorSuppress) { $type = $node->expr->getAttribute('type'); } elseif ($node instanceof Expr\Eval_) { $type = Type::mixed_(); } elseif ($node instanceof Expr\Exit_) { $type = Type::null_(); } if ($type !== null || !$node->hasAttribute('type')) { if ($type === null) { $type = Type::mixed_(); } $node->setAttribute('type', $type); } }
/** * @param string $name * @param string $text */ protected function __construct($name, $text) { parent::__construct($name, $text); $text = trim($text); $parts = preg_split('~\\s+~', trim($text), 2); $this->type = Type::fromString($parts[0]); if (count($parts) >= 2) { $this->description = $parts[1] ?: null; } }
public function test_StaticPropertyFetch_self() { $prop = (new Property())->setName('x')->setStatic(true)->setType(Type::int_()); $refl = $this->getMockBuilder(Reflection::class)->disableOriginalConstructor()->getMock(); $refl->expects($this->once())->method('findProperty')->with($this->equalTo('\\C'), $this->equalTo('$x'))->willReturn($prop); $expr = new Expr\StaticPropertyFetch(new Name('self'), 'x'); $class = new Stmt\Class_('C', ['stmts' => [new Stmt\ClassMethod('f', ['stmts' => [$expr]])]], ['namespacedName' => new Name\FullyQualified('C')]); $this->infer([$class], $refl); $this->assertTrue($expr->getAttribute('type')->equals(Type::int_())); $this->assertSame([$prop], $expr->getAttribute('reflections')); }
public function test_Ternary() { $var1 = new Expr\Variable('a', ['type' => Type::array_()]); $var2 = new Expr\Variable('b', ['type' => Type::int_()]); $var3 = new Expr\Variable('c', ['type' => Type::float_()]); $expr = new Expr\Ternary($var1, $var2, $var3); $this->infer([$expr]); $this->assertTrue($expr->getAttribute('type')->equals(Type::alternatives([Type::int_(), Type::float_()]))); $expr = new Expr\Ternary($var1, null, $var3); $this->infer([$expr]); $this->assertTrue($expr->getAttribute('type')->equals(Type::alternatives([Type::array_(), Type::float_()]))); }
public function test_getType() { $expr = new Expr\Variable('qaz', ['type' => Type::int_()]); $container = new Container(); $parser = $this->getMockBuilder(Parser::class)->disableOriginalConstructor()->getMock(); $parser->method('getNodes')->willReturn([]); $parser->method('getNodesAtOffset')->with($this->equalTo(7))->willReturn([$expr]); $resolver = $this->getMockBuilder(NameResolver::class)->disableOriginalConstructor()->getMock(); $container->set('parser', $parser); $container->set('name_resolver', $resolver); $this->assertTrue(Type::int_()->equals((new TypeInferrer($container))->getType(7))); }
protected function resolveDocTagType(Type $type) { return $type->walk(function (Type $type) { if ($type instanceof ObjectType) { $nameStr = $type->getClass(); if ($nameStr !== null && $nameStr !== '') { if ($nameStr[0] === '\\') { $name = new FullyQualified(substr($nameStr, 1)); } else { $name = new Name($nameStr); } $name = $this->resolveClassName($name)->getAttribute('resolved'); $nameStr = $name->toString(); if ($name instanceof FullyQualified) { $nameStr = '\\' . $nameStr; } return Type::object_($nameStr); } } return $type; }); }
public function getGoToLocations($offset, $nodes) { $this->run(); $name = null; $node = null; if (count($nodes) >= 2 && $nodes[0] instanceof Name) { $name = Type::nameToString($nodes[0]); $node = $nodes[1]; } $locations = []; if ($node !== null && ($node instanceof Expr\Instanceof_ || $node instanceof Expr\New_ || $node instanceof Stmt\Catch_ || $node instanceof Stmt\Function_ || $node instanceof Stmt\ClassMethod || $node instanceof Expr\Closure || $node instanceof Param || $node instanceof Stmt\Class_ || $node instanceof Stmt\Interface_ || $node instanceof Stmt\TraitUse || $node instanceof Stmt\TraitUseAdaptation)) { foreach ($this->reflection->findClass($name) as $class) { if ($class->getLocation() !== null) { $locations[] = $class->getLocation(); } } } return $locations; }
public function test_StaticCall() { $class = (new Class_())->setName('\\C'); $method = (new Method())->setName('qaz')->setClass($class)->setStatic(true)->setDocReturnType(Type::object_('\\X\\Y')); $prop = (new Property())->setName('$wsx')->setClass($class)->setStatic(true)->setType(Type::string_()); $const = (new ClassConst())->setName('EDC')->setClass($class); $cls = new Name\FullyQualified('C'); $id = new Identifier('q'); $expr = new Expr\StaticCall($cls, $id, []); $completions = $this->complete([$id, $expr], [$method], [$prop], [$const]); $this->assertCount(3, $completions); $this->assertSame('qaz', $completions[0]->getInsertion()); $this->assertSame('qaz()', $completions[0]->getDisplay()); $this->assertSame('static_method', $completions[0]->getKind()); $this->assertSame('Y', $completions[0]->getType()); $this->assertSame('EDC', $completions[1]->getInsertion()); $this->assertSame('EDC', $completions[1]->getDisplay()); $this->assertSame('class_const', $completions[1]->getKind()); $this->assertSame('$wsx', $completions[2]->getInsertion()); $this->assertSame('$wsx', $completions[2]->getDisplay()); $this->assertSame('static_property', $completions[2]->getKind()); $this->assertSame('string', $completions[2]->getType()); }
protected function makeMethod($name, $visibility = ClassLike::M_PUBLIC) { return (new Method())->setName($name)->setReturnType(Type::mixed_())->setDocReturnType(Type::mixed_())->setAccessibility($visibility); }
public function leaveNode(Node $node) { if ($node instanceof Stmt\Function_ || $node instanceof Stmt\ClassMethod || $node instanceof Expr\Closure) { array_pop($this->functionScopeStack); } elseif ($node instanceof Stmt\ClassLike) { array_pop($this->classStack); array_pop($this->functionScopeStack); } if (!$node instanceof Expr) { return; } $type = null; $reflections = null; if ($node instanceof Expr\Variable) { if (is_string($node->name)) { if ($node->name === 'this') { $type = $this->getCurrentClass(); } elseif (array_key_exists('$' . $node->name, $this->getCurrentFunctionScope())) { $type = $this->getCurrentFunctionScope()['$' . $node->name]; } } } elseif ($node instanceof Expr\FuncCall) { $reflections = []; if ($node->name instanceof Name) { $reflections = $this->reflection->findFunction(Type::nameToString($node->name)); } else { $reflections = $this->findMethods($node->name->getAttribute('type'), '__invoke'); } $type = $this->functionsReturnType($reflections); // TODO: ConstFetch } elseif ($node instanceof Expr\MethodCall) { $reflections = []; if (is_string($node->name) || $node->name instanceof Identifier) { $reflections = $this->findMethods($node->var->getAttribute('type'), (string) $node->name); } $type = $this->functionsReturnType($reflections); } elseif ($node instanceof Expr\StaticCall) { $reflections = []; if ($node->class instanceof Name && (is_string($node->name) || $node->name instanceof Identifier)) { $reflections = $this->findMethods(Type::object_(Type::nameToString($node->class)), (string) $node->name, true); } $type = $this->functionsReturnType($reflections); } elseif ($node instanceof Expr\PropertyFetch) { $reflections = []; if (is_string($node->name) || $node->name instanceof Identifier) { $reflections = $this->findProperties($node->var->getAttribute('type'), '$' . (string) $node->name); } $type = $this->variablesType($reflections); } elseif ($node instanceof Expr\StaticPropertyFetch) { $reflections = []; if ($node->class instanceof Name && is_string($node->name)) { $reflections = $this->findProperties(Type::object_(Type::nameToString($node->class)), '$' . $node->name, true); } $type = $this->variablesType($reflections); } elseif ($node instanceof Expr\ClassConstFetch) { // TODO ::class $reflections = []; if ($node->class instanceof Name) { $reflections = $this->findClassConsts(Type::object_(Type::nameToString($node->class)), (string) $node->name); } $type = $this->constsType($reflections); } elseif ($node instanceof Expr\ArrayDimFetch) { $arrayType = $node->var->getAttribute('type'); $altTypes = [$arrayType]; if ($arrayType instanceof AlternativesType) { $altTypes = $arrayType->getAlternatives(); } $types = []; foreach ($altTypes as $altType) { if ($altType instanceof ArrayType) { $types[] = $altType->getValueType(); } elseif ($altType instanceof ObjectType) { $types[] = $this->functionsReturnType($this->findMethods($altType, 'offsetGet')); // TODO: check for ArrayAccess } } $type = $types !== [] ? Type::alternatives($types) : Type::mixed_(); } elseif ($node instanceof Expr\Array_) { $type = Type::array_(); } elseif ($node instanceof Expr\New_) { if ($node->class instanceof Name) { $type = Type::object_(Type::nameToString($node->class)); } else { $type = Type::object_(); } } elseif ($node instanceof Expr\Clone_) { $type = $node->expr->getAttribute('type'); } elseif ($node instanceof Expr\Closure) { $type = Type::object_('\\Closure'); } elseif ($node instanceof Expr\Cast\Array_) { $exprType = $node->expr->getAttribute('type'); $altTypes = [$exprType]; if ($exprType instanceof AlternativesType) { $altTypes = $exprType->getAlternatives(); } $types = []; foreach ($altTypes as $altType) { if ($altType instanceof ArrayType) { $types[] = $altType; } else { $types[] = Type::array_(); // TODO primitives: (array)int --> int[] } } $type = $types !== [] ? Type::alternatives($types) : Type::mixed_(); } elseif ($node instanceof Expr\Cast\Object_) { $exprType = $node->expr->getAttribute('type'); $altTypes = [$exprType]; if ($exprType instanceof AlternativesType) { $altTypes = $exprType->getAlternatives(); } $types = []; foreach ($altTypes as $altType) { if ($altType instanceof ObjectType) { $types[] = $altType; } else { $types[] = Type::object_('\\stdClass'); } } $type = $types !== [] ? Type::alternatives($types) : Type::mixed_(); } elseif ($node instanceof Expr\Include_) { $type = Type::mixed_(); // TODO: Yield_ // TODO: YieldFrom } if ($type !== null) { $node->setAttribute('type', $type); } if ($reflections !== null) { $node->setAttribute('reflections', $reflections); } }
/** * @param Interface_ $interface * @param Stmt\Interface_ $node */ protected function processInterface(Interface_ $interface, Stmt\Interface_ $node) { $this->processClassLike($interface, $node); foreach ($node->extends as $extends) { $interface->addExtends(Type::nameToString($extends)); } }
public function enterNode(Node $node) { array_push($this->nodePathFromTop, $node); $classes = []; if ($node instanceof Expr\Instanceof_ || $node instanceof Expr\New_ || $node instanceof Expr\ClassConstFetch || $node instanceof Expr\StaticPropertyFetch || $node instanceof Expr\StaticCall) { $classes[] = $node->class; } elseif ($node instanceof Stmt\Catch_ || $node instanceof Param) { $classes[] = $node->type; } elseif ($node instanceof Stmt\Function_ || $node instanceof Stmt\ClassMethod || $node instanceof Expr\Closure) { $classes[] = $node->returnType; } elseif ($node instanceof Stmt\Class_) { $classes[] = $node->extends; $classes = array_merge($classes, $node->implements); } elseif ($node instanceof Stmt\Interface_) { $classes = array_merge($classes, $node->extends); } elseif ($node instanceof Stmt\TraitUse) { $classes = array_merge($classes, $node->traits); } elseif ($node instanceof Stmt\TraitUseAdaptation) { $classes[] = $node->trait; } foreach ($classes as $class) { if (is_object($class) && $class instanceof Name) { $name = Type::nameToString($class); if (!in_array(strtolower($name), ['self', 'parent', 'static']) && empty($this->reflection->findClass($name))) { $range = Range::fromNode($class, $this->file->getPath()); $fixes = []; if ($this->namespaceReflection !== null && $class->isUnqualified()) { foreach ($this->namespaceReflection->findFullyQualifiedClasses($class->toString()) as $fqname) { //$fixes[] = new Fix([new FixChunk($range, $fqname)], $fqname); $fixes[] = $this->getFix($fqname); } } $this->diagnostics[] = new Diagnostic([$range], 'Undefined class', $fixes); } } } }
protected function handleFunction(Function_ $function, array $data) { $this->handleElement($function, $data); $function->setReturnType(Type::mixed_()); $function->setDocReturnType($this->getType($data['return_type'])); $function->setReturnByRef($data['return_by_ref']); foreach ($data['params'] as $paramData) { $param = new Param(); $param->setName($paramData['name']); $param->setTypeHint(Type::mixed_()); $param->setDocType($this->getType($paramData['type'])); $param->setOptional($paramData['optional']); $param->setByRef($paramData['by_ref']); $param->setVariadic($paramData['variadic']); $function->addParam($param); } }
public function complete($offset) { $this->run(); $nodes = $this->parser->getNodesAtOffset($offset, true); $node = null; if (count($nodes) > 0) { $node = $nodes[0]; if ($node instanceof Identifier || $node instanceof ErrorNode\Nothing) { $node = count($nodes) > 1 ? $nodes[1] : null; } } $completions = []; $ctxClass = null; foreach ($nodes as $ctxNode) { if ($ctxNode instanceof Stmt\ClassLike) { $ctxClass = $ctxNode->hasAttribute('namespacedName') ? Type::nameToString($ctxNode->getAttribute('namespacedName')) : $node->name; break; } } if ($node instanceof Expr\MethodCall || $node instanceof Expr\PropertyFetch) { $methods = $this->findMethods($node->var->getAttribute('type')); $properties = $this->findProperties($node->var->getAttribute('type')); $methods = $this->reflection->filterAvailableMembers($methods, $ctxClass); $properties = $this->reflection->filterAvailableMembers($properties, $ctxClass); $completions = array_merge($this->formatMethods($methods), $this->formatProperties($properties)); } elseif ($node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch || $node instanceof Expr\ClassConstFetch) { // TODO: Support static call on an object: $object::staticMethod() etc. // TODO: Filter out non-static members outside the inheritance // chain - while static calls to them are allowed in PHP, they // are pretty useless. $staticOnly = true; foreach ($nodes as $ctxNode) { if ($ctxNode instanceof Stmt\ClassMethod) { $staticOnly = $ctxNode->isStatic(); break; } } $methods = $this->findMethods(Type::object_(Type::nameToString($node->class)), $staticOnly); $properties = $this->findProperties(Type::object_(Type::nameToString($node->class)), $staticOnly); $consts = $this->findClassConsts(Type::object_(Type::nameToString($node->class))); $methods = $this->reflection->filterAvailableMembers($methods, $ctxClass); $properties = $this->reflection->filterAvailableMembers($properties, $ctxClass); $completions = array_merge($this->formatMethods($methods), $this->formatClassConsts($consts), $this->formatProperties($properties, true)); } return $completions; }
/** * @param Method[] $methods * @param Method $method */ protected function mergeMethod(array &$methods, Method $method) { // TODO: Do a better job here? $method->setDocReturnType(Type::alternatives([$method->getDocReturnType(), $method->getReturnType()])); $params = []; foreach ($method->getParams() as $param) { $param->setDocType(Type::alternatives([$param->getDocType(), $param->getTypeHint()])); $params[$param->getName()] = $param; } if (isset($methods[strtolower($method->getName())])) { $baseMethod = $methods[strtolower($method->getName())]; $method->setDocReturnType(Type::alternatives([$method->getDocReturnType(), $baseMethod->getDocReturnType()])); foreach ($baseMethod->getParams() as $baseParam) { if (array_key_exists($baseParam->getName(), $params)) { $param = $params[$baseParam->getName()]; $param->setDocType(Type::alternatives([$param->getDocType(), $baseParam->getDocType()])); } } } $methods[strtolower($method->getName())] = $method; }