private function inferTypesForParameter(Parameter $param, AbstractFunction $function, Clazz $clazz = null) { if (($type = $param->getPhpType()) && !$type->isUnknownType() && !$type->isNullType() && !$type->isFalse() && !$type->isAllType()) { return; } $allowsNull = $type && $type->isNullType(); if (null !== ($node = $param->getAstNode())) { $type = $this->parser->getTypeFromParamAnnotation($node, $param->getName()); // Check whether we can use the type of a overridden method. // We only do this if we find a parent method that has been defined // on an interface (I), or if we find an abstract method (II). if (null === $type && $function instanceof Method && null !== $clazz) { foreach ($this->findOverriddenMethodsForDocInheritance($function->getName(), $clazz) as $parentMethod) { if (null !== ($docType = $parentMethod->getParamDocType($param->getIndex()))) { $type = $this->parser->getType($docType); break; } } } } if (null === $type) { $type = $this->registry->getNativeType('unknown'); } else { if ($allowsNull) { $type = $this->registry->createNullableType($type); } } $param->setPhpType($type); if ($node) { $node->setAttribute('type', $type); } }
/** * Returns the inferred return type for the given function. * * If we cannot infer any type NO type is returned, this is also returned if we can only infer * ALL type, or UNKNOWN type. * * We use two characteristics for inferring a return type. First, we are looking at how the * return value of the function is used in the places from where it is called. Currently, we * are only looking at whether it is passed to other functions/methods and what their expected * parameter types are. * * Second, we also take a look at the exit points of the CFG of the function itself to see * whether there are some specific types which we can infer. For example, a function might return * NULL type, or ALL type in which case its return type would normally be set to ALL type. * However, for our analysis here, we just ignore the ALL type, and add the NULL type to the * list of allowed types. * * @param AbstractFunction $function * * @return PhpType */ private function inferReturnTypeForFunction(AbstractFunction $function, MethodContainer $container = null) { $types = array(); if ($container instanceof \Scrutinizer\PhpAnalyzer\Model\InterfaceC) { foreach ($container->getImplementingClasses() as $class) { if (null === ($implementedFunction = $class->getMethod($function->getName()))) { continue; } $returnType = $implementedFunction->getReturnType(); if ($returnType->isUnknownType() || $returnType->isAllType()) { continue; } $types[] = $returnType; } } if (!$types) { foreach ($function->getInCallSites() as $site) { // We can only analyze call sites which are part of the code which is currently being // scanned. Otherwise, we would need to persist the possible types to the database. // This could be a future improvement though if we deem it necessary. if (null === ($node = $site->getAstNode())) { continue; } if (null === ($parent = $node->getAttribute('parent'))) { continue; } switch (true) { case $parent instanceof \PHPParser_Node_Arg: if (null === ($callNode = $parent->getAttribute('parent'))) { break; } $paramType = $this->getSpecificParamTypeForArg($callNode, $parent); if ($paramType) { $types[] = $paramType; } break; case $parent instanceof \PHPParser_Node_Expr_Assign: case $parent instanceof \PHPParser_Node_Expr_AssignRef: foreach ($parent->var->getAttribute('maybe_using_vars', array()) as $varNode) { if (null === ($argNode = $varNode->getAttribute('parent'))) { continue; } if (!$argNode instanceof \PHPParser_Node_Arg) { continue; } $callNode = $argNode->getAttribute('parent'); $paramType = $this->getSpecificParamTypeForArg($callNode, $argNode); if (null !== $paramType) { $types[] = $paramType; } } break; } } } $cfa = new \Scrutinizer\PhpAnalyzer\ControlFlow\ControlFlowAnalysis(); $cfa->process($function->getAstNode()); $cfg = $cfa->getGraph(); foreach ($cfg->getDirectedSuccNodes($cfg->getImplicitReturn()) as $exitGraphNode) { $exitNode = $exitGraphNode->getAstNode(); if (!$exitNode instanceof \PHPParser_Node_Stmt_Return || null === $exitNode->expr) { $types[] = $this->registry->getNativeType('null'); continue; } $returnType = $exitNode->expr->getAttribute('type'); if ($returnType && !$returnType->isUnknownType() && !$returnType->isAllType()) { $types[] = $returnType; } } return $this->refineTypeForAnnotation($this->registry->createUnionType($types), true); }