private function assertUnion($expected) { $builder = new UnionTypeBuilder($this->registry); $types = func_get_args(); $types = array_splice($types, 1); foreach ($types as $type) { $type = $this->resolveType($type); $builder->addAlternate($type); } $this->assertEquals($expected, (string) $builder->build()); }
private function inferTypeForParameter(AbstractFunction $function, Parameter $param) { $builder = new UnionTypeBuilder($this->typeRegistry); $index = $param->getIndex(); foreach ($function->getInCallSites() as $site) { $args = $site->getArgs(); if (!isset($args[$index])) { continue; } $builder->addAlternate($args[$index]->getPhpType()); } $newType = $builder->build(); if (!$newType->isNoType() && !$newType->isUnknownType()) { $param->setPhpType($newType); } }
private function checkParameterAssignment(\PHPParser_Node_Param $node) { $builder = new UnionTypeBuilder($this->typeRegistry); if (null !== $node->default) { $this->attachLiteralTypes($node->default); $type = $node->default->getAttribute('type'); if ($type) { $builder->addAlternate($type); } } if ('array' === $node->type) { $type = $this->typeRegistry->getNativeType('array'); // If the annotated type also contains the array type, we use the annotated // type as it is potentially more specific, e.g. "array<string>" or "array|null". $annotatedType = $this->commentParser->getTypeFromParamAnnotation($node->getAttribute('parent'), $node->name); if (null !== ($containedArrayType = $this->getContainedArrayType($annotatedType))) { $type = $containedArrayType; } $builder->addAlternate($type); } else { if ($node->type instanceof \PHPParser_Node_Name) { $builder->addAlternate($this->typeRegistry->getClassOrCreate(implode("\\", $node->type->parts))); } } $type = $builder->build(); if ($type->isNoType()) { $type = $this->commentParser->getTypeFromParamAnnotation($node->getAttribute('parent'), $node->name); } else { if ($type->isNullType()) { $commentType = $this->commentParser->getTypeFromParamAnnotation($node->getAttribute('parent'), $node->name); if ($commentType) { $type = $this->typeRegistry->createNullableType($commentType); } else { $type = null; } } else { if ($type->isBooleanType()) { $commentType = $this->commentParser->getTypeFromParamAnnotation($node->getAttribute('parent'), $node->name); if ($commentType) { $type = $this->typeRegistry->createUnionType(array($type, $commentType)); } } } } if (null === $type) { $type = $this->typeRegistry->getNativeType('unknown'); } $node->setAttribute('type', $type); // This could be the case if the same name is used twice as parameter. if (false === $this->scope->isDeclared($node->name)) { $var = $this->scope->declareVar($node->name, $type, null === $type); $var->setNameNode($node); $var->setReference($node->byRef); } else { $var = $this->scope->getVar($node->name); if ($varType = $var->getType()) { $var->setType($this->typeRegistry->createUnionType(array($varType, $type))); } else { if (null !== $type) { $var->setType($type); $var->setTypeInferred(true); } } $var->setReference($node->byRef); } }
/** * Creates a new union with the given types. * * If strings are passed, they are assumed to be native types. * * @param array $types * * @return UnionType */ public function createUnionType(array $types) { $builder = new UnionTypeBuilder($this); foreach ($types as $type) { if (!$type instanceof PhpType) { $nativeType = $this->getNativeType($type); if (null === $nativeType) { throw new \InvalidArgumentException(sprintf('There is no native type named "%s".', $type)); } $type = $nativeType; } assert($type instanceof PhpType); $builder->addAlternate($type); } return $builder->build(); }
public function enterScope(NodeTraversal $t) { $scope = $t->getScope(); $root = $scope->getRootNode(); if (!$root instanceof \PHPParser_Node_Stmt_Function && !$root instanceof \PHPParser_Node_Stmt_ClassMethod) { return; } // Bail out on abstract methods. if ($root instanceof \PHPParser_Node_Stmt_ClassMethod && ($root->type & \PHPParser_Node_Stmt_Class::MODIFIER_ABSTRACT) !== 0) { return; } // Bail out on methods defined on interfaces. if ($root instanceof \PHPParser_Node_Stmt_ClassMethod && $root->getAttribute('parent')->getAttribute('parent') instanceof \PHPParser_Node_Stmt_Interface) { return; } // Bail out on built-in functions marked by the @jms-builtin annotation. // For these, we will solely infer types from doc comments. if (false !== strpos($root->getDocComment(), '@jms-builtin')) { return; } // Same as above, but for methods of classes marked with @jms-builtin. if ($root instanceof \PHPParser_Node_Stmt_ClassMethod) { $maybeClass = $root->getAttribute('parent')->getAttribute('parent'); if ($maybeClass instanceof \PHPParser_Node_Stmt_Class && false !== strpos($maybeClass->getDocComment(), '@jms-builtin')) { return; } } $cfg = $t->getControlFlowGraph(); $builder = new UnionTypeBuilder($this->typeRegistry); foreach ($cfg->getNode(null)->getInEdges() as $edge) { $sourceNode = $edge->getSource()->getAstNode(); if (!$sourceNode instanceof \PHPParser_Node_Stmt_Return) { $builder->addAlternate($this->typeRegistry->getNativeType('null')); continue; } // If there is no type information available, we cannot make any // assumptions for this function/method. if (!($type = $sourceNode->getAttribute('type'))) { return; } $builder->addAlternate($type); } $type = $builder->build(); if ($type->isUnknownType()) { return; } $function = $this->typeRegistry->getFunctionByNode($root); if ($function instanceof GlobalFunction) { if ($this->hasTypeChanged($type, $function->getReturnType())) { $this->repeatedPass->repeat(); } $function->setReturnType($type); } else { if ($function instanceof ContainerMethodInterface) { $method = $function->getMethod(); if ($this->hasTypeChanged($type, $method->getReturnType())) { $this->repeatedPass->repeat(); } $method->setReturnType($type); } } }