private function checkUnusedMethod(NodeTraversal $t, \PHPParser_Node_Stmt_ClassMethod $node)
 {
     if ((\PHPParser_Node_Stmt_Class::MODIFIER_PRIVATE & $node->type) === 0) {
         return;
     }
     $classType = $t->getScope()->getTypeOfThis()->restrictByNotNull();
     if (($method = $classType->toMaybeObjectType()->getMethod($node->name)) && 0 === count($method->getInMethodCallSites())) {
         $this->phpFile->addComment($node->getLine(), Comment::warning('cleanup.unused_method', 'This method is unused, and could be removed.'));
     }
 }
 public function shouldTraverse(NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null)
 {
     $graphNode = $t->getControlFlowGraph()->getNode($node);
     if (null !== $graphNode && GraphReachability::UNREACHABLE === $graphNode->getAttribute(GraphReachability::ATTR_REACHABILITY)) {
         // Only report error when there are some line number informations.
         // There are synthetic nodes with no line number informations, nodes
         // introduced by other passes (although not likely since this pass should
         // be executed early) or some PHPParser bug.
         if (-1 !== $node->getLine()) {
             $this->file->addComment($node->getLine(), Comment::warning('usage.unreachable_code', '``%unreachable_code%`` is not reachable.', array('unreachable_code' => $this->prettyPrinter->prettyPrint(array($node)))));
             // From now on, we are going to assume the user fixed the error and not
             // give more warnings related to code sections reachable from this node.
             $r = new GraphReachability($t->getControlFlowGraph());
             $r->recompute($node);
             // Saves time by not traversing children.
             return false;
         }
     }
     return true;
 }
 private function handleEmptyCatch(\PHPParser_Node_Stmt_Catch $node)
 {
     if (!$this->getSetting('non_commented_empty_catch_block')) {
         return;
     }
     if (count($node->stmts) > 0) {
         return;
     }
     if (-1 === ($line = $node->getLine())) {
         return;
     }
     $code = explode("\n", $this->phpFile->getContent());
     $previousBlock = $node->getAttribute('parent')->stmts;
     foreach ($previousBlock as $lastChild) {
     }
     $previousLineEnd = count($previousBlock) > 0 ? $lastChild->getLine() : $previousBlock->getLine();
     // Not sure how readable that code is supposed to be, but it doesn't seem to look good.
     if ($previousLineEnd === $node->getLine()) {
         $this->phpFile->addComment($previousLineEnd, Comment::warning('coding_style.unreadable_catch', 'Consider moving this CATCH statement to a new line.'));
         return;
     }
     // Start scanning for a comment one line after the last statement of the previous block.
     $foundComment = $afterCatch = $inCatchBlock = $terminateOnNextNonWhitespace = $afterCondition = false;
     for ($i = $previousLineEnd, $c = count($code); $i < $c; $i++) {
         if (empty($code[$i])) {
             continue;
         }
         // Might break for some weird inlined HTML, but honestly who does that these days?
         $lineTokens = token_get_all('<?php ' . $code[$i]);
         foreach ($lineTokens as $token) {
             if (!is_array($token)) {
                 if ($terminateOnNextNonWhitespace) {
                     break 2;
                 }
                 if ($afterCondition && '{' === $token) {
                     $inCatchBlock = true;
                     continue;
                 }
                 if ($inCatchBlock && '}' === $token) {
                     $terminateOnNextNonWhitespace = true;
                     continue;
                 }
                 if ($afterCatch && ')' === $token) {
                     $afterCondition = true;
                     continue;
                 }
                 // no whitespace and after the catch block means something like
                 // catch (\Exception $ex) 'foo';
                 if ($afterCondition && !$inCatchBlock) {
                     break;
                 }
                 continue;
             }
             if (\T_DOC_COMMENT === $token[0] || \T_COMMENT === $token[0]) {
                 $foundComment = true;
                 break 2;
             }
             if (\T_WHITESPACE === $token[0]) {
                 continue;
             }
             if ($terminateOnNextNonWhitespace) {
                 break 2;
             }
             if (\T_CATCH === $token[0]) {
                 $afterCatch = true;
                 continue;
             }
         }
     }
     if (!$foundComment) {
         $this->phpFile->addComment($node->getLine(), Comment::warning('suspicious_code.empty_catch_block', 'Consider adding a comment why this CATCH block is empty.'));
     }
 }
 private function checkForMissingProperty(\PHPParser_Node_Expr_PropertyFetch $node)
 {
     if (!is_string($node->name)) {
         return;
     }
     if (!($objType = $node->var->getAttribute('type'))) {
         return;
     }
     $objType = $objType->restrictByNotNull()->toMaybeObjectType();
     if (!$objType) {
         // TODO: Add support to check on union types.
         return;
     }
     if ($objType->isInterface()) {
         $this->phpFile->addComment($node->getLine(), Comment::warning('types.property_access_on_interface', 'Accessing "%property_name%" on the interface "%interface_name%" suggest that you code against a concrete implementation. How about adding an ``instanceof`` check?', array('property_name' => $node->name, 'interface_name' => $objType->getName())));
         return;
     }
     if (!$objType->isNormalized() || $objType->hasProperty($node->name)) {
         return;
     }
     // Ignore all property accesses on ``stdClass`` objects. Currently, we have
     // no way to describe, or track RECORD types. As such, any messages related to
     // stdClass are likely wrong, but at least there are too many false-positives
     // for now. So, we just disable this.
     if ($objType->isSubtypeOf($this->typeRegistry->getClassOrCreate('stdClass'))) {
         return;
     }
     // Ignore all property reads on ``SimpleXMLElement`` objects. The reasoning
     // behind this is similar to the exception for ``stdClass`` objects above.
     // We simply have no reliable way to describe the structure of these objects
     // for now. So, we disable checks for them to avoid a flood of false-positives.
     if (!\Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::isAssignmentOp($node->getAttribute('parent')) && $objType->isSubtypeOf($this->typeRegistry->getClassOrCreate('SimpleXMLElement'))) {
         return;
     }
     // Property accesses inside isset() are safe, do not make any noise just yet.
     if ($node->getAttribute('parent') instanceof \PHPParser_Node_Expr_Isset) {
         return;
     }
     if (null !== ($bestName = $this->getMostSimilarName($node->name, $objType->getPropertyNames()))) {
         $this->phpFile->addComment($node->getLine(), Comment::error('typos.mispelled_property_name', 'The property "%offending_property_name%" does not exist. Did you mean "%closest_property_name%"?', array('offending_property_name' => $node->name, 'closest_property_name' => $bestName)));
     } else {
         // Ignore additional accesses to this property.
         $objType->addProperty(new Property($node->name));
         if (\Scrutinizer\PhpAnalyzer\PhpParser\NodeUtil::isAssignmentOp($node->getAttribute('parent'))) {
             if ($objType->hasMethod('__set')) {
                 $this->phpFile->addComment($node->getLine(), Comment::warning('strict.maybe_undocument_property_write_capability', 'The property ``%property_name%`` does not exist. Since you implemented ``__set``, maybe consider adding a [@property annotation](http://www.phpdoc.org/docs/latest/for-users/tags/property.html).', array('property_name' => $node->name)));
                 return;
             }
         } else {
             if ($objType->hasMethod('__get')) {
                 $this->phpFile->addComment($node->getLine(), Comment::warning('strict.maybe_undocument_property_read_capability', 'The property ``%property_name%`` does not exist. Since you implemented ``__get``, maybe consider adding a [@property annotation](http://www.phpdoc.org/docs/latest/for-users/tags/property.html).', array('property_name' => $node->name)));
                 return;
             }
         }
         $thisType = $this->t->getScope()->getTypeOfThis();
         if ($thisType && $thisType->equals($objType)) {
             $this->phpFile->addComment($node->getLine(), Comment::warning('strict.undeclared_property', 'The property "%property_name%" does not exist. Did you maybe forget to declare it?', array('property_name' => $node->name)));
         } else {
             $this->phpFile->addComment($node->getLine(), Comment::error('typos.non_existent_property', 'The property "%property_name%" does not exist.', array('property_name' => $node->name)));
         }
     }
 }
 public function visit(NodeTraversal $t, \PHPParser_Node $node, \PHPParser_Node $parent = null)
 {
     // Consider only non-dynamic variables in this pass.
     if (!$node instanceof \PHPParser_Node_Expr_Variable || !is_string($node->name)) {
         return;
     }
     // We ignore PHP globals.
     if (NodeUtil::isSuperGlobal($node)) {
         return;
     }
     // Ignore variables which are defined here.
     if ($parent instanceof \PHPParser_Node_Expr_Assign && $parent->var === $node) {
         return;
     }
     if (!$t->getScope()->isDeclared($node->name)) {
         if ($bestName = CheckForTyposPass::getMostSimilarName($node->name, $t->getScope()->getVarNames())) {
             $this->phpFile->addComment($node->getLine(), Comment::error('typos.mispelled_variable_name', 'The variable ``%offending_variable_name%`` does not exist. Did you mean ``%closest_variable_name%``?', array('offending_variable_name' => '$' . $node->name, 'closest_variable_name' => '$' . $bestName)));
         } else {
             $this->phpFile->addComment($node->getLine(), Comment::warning('usage.undeclared_variable', 'The variable ``%variable_name%`` does not exist. Did you forget to declare it?', array('variable_name' => '$' . $node->name)));
         }
         return;
     }
     if ($parent instanceof \PHPParser_Node_Expr_ArrayDimFetch && $node->getAttribute('array_initializing_variable') && $parent->var === $node) {
         $this->phpFile->addComment($node->getLine(), Comment::warning('usage.non_initialized_array', '``%array_fetch_expr%`` was never initialized. Although not strictly required by PHP, it is generally a good practice to add ``%array_fetch_expr% = array();`` before regardless.', array('array_fetch_expr' => self::$prettyPrinter->prettyPrintExpr($node))));
     }
 }
 private function markOffense($stmtName)
 {
     $this->phpFile->addComment($this->stream->token->getLine(), Comment::warning('coding_style.blocks_should_have_braces', 'Please always use braces to surround the code block of %statement_name% statements.', array('statement_name' => $stmtName)));
 }
 /**
  * @param integer $line
  * @param string  $type
  */
 private function checkNaming($line, $code, $name, $type)
 {
     if (!$this->getSetting('naming.enabled')) {
         return;
     }
     if (!preg_match('#' . str_replace('#', '\\#', $this->getSetting('naming.' . $type)) . '#', $name)) {
         $this->phpFile->addComment($line, Comment::warning('coding_style.naming', '``%code%`` does not seem to conform to the naming convention (``%regex%``).', array('code' => $code, 'regex' => $this->getSetting('naming.' . $type)))->varyIn(array('regex')));
     }
 }
 private function checkParameters(\PHPParser_Node $node, AbstractFunction $function, array $args, \Scrutinizer\PhpAnalyzer\Model\MethodContainer $clazz = null)
 {
     $missingArgs = $this->argumentChecker->getMissingArguments($function, $args, $clazz);
     if (!empty($missingArgs)) {
         if (count($missingArgs) > 1) {
             $this->phpFile->addComment($node->getLine(), Comment::error('usage.missing_multiple_required_arguments', 'The call to ``%function_name%()`` misses some required arguments starting with ``$%parameter_name%``.', array('function_name' => $function->getName(), 'parameter_name' => reset($missingArgs))));
         } else {
             $this->phpFile->addComment($node->getLine(), Comment::error('usage.missing_required_argument', 'The call to ``%function_name%()`` misses a required argument ``$%parameter_name%``.', array('function_name' => $function->getName(), 'parameter_name' => reset($missingArgs))));
         }
     }
     if ('disabled' !== $this->getSetting('argument_type_checks')) {
         $argTypes = array();
         foreach ($args as $arg) {
             $argTypes[] = $arg->getAttribute('type') ?: $this->typeRegistry->getNativeType('unknown');
         }
         $mismatchedTypes = $this->argumentChecker->getMismatchedArgumentTypes($function, $argTypes, $clazz);
         foreach ($mismatchedTypes as $index => $type) {
             $this->phpFile->addComment($args[$index]->getLine(), Comment::warning('usage.argument_type_mismatch', '``%expr%`` of type ``%expr_type%`` is not a sub-type of ``%expected_type%``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($args[$index]->value), 'expr_type' => (string) $argTypes[$index], 'expected_type' => (string) $type)));
         }
     }
 }
 private function tryRemoveAssignment(NodeTraversal $t, \PHPParser_Node $n, \PHPParser_Node $exprRoot = null, $inState, $outState)
 {
     $parent = $n->getAttribute('parent');
     if (NodeUtil::isAssignmentOp($n)) {
         $lhs = $n->var;
         $rhs = $n->expr;
         if ($n instanceof \PHPParser_Node_Expr_AssignList) {
             $i = 0;
             foreach ($n->vars as $var) {
                 if (null === $var) {
                     $i += 1;
                     continue;
                 }
                 $newNode = new \PHPParser_Node_Expr_Assign($var, new \PHPParser_Node_Expr_ArrayDimFetch($rhs, new \PHPParser_Node_Scalar_LNumber($i)), $n->getLine());
                 $newNode->setAttribute('is_list_assign', true);
                 $this->tryRemoveAssignment($t, $newNode, $exprRoot, $inState, $outState);
                 $i += 1;
             }
             return;
         }
         // Recurse first. Example: dead_x = dead_y = 1; We try to clean up dead_y first.
         if (null !== $rhs) {
             $this->tryRemoveAssignment($t, $rhs, $exprRoot, $inState, $outState);
             $rhs = $lhs->getAttribute('next');
         }
         $scope = $t->getScope();
         if (!$lhs instanceof \PHPParser_Node_Expr_Variable || !is_string($lhs->name)) {
             return;
         }
         if (!$scope->isDeclared($lhs->name)) {
             return;
         }
         if (in_array($lhs->name, NodeUtil::$superGlobalNames, true)) {
             return;
         }
         $var = $scope->getVar($lhs->name);
         $escaped = $this->liveness->getEscapedLocals();
         if (isset($escaped[$var])) {
             return;
             // Local variable that might be escaped due to closures.
         }
         // If we have an identity assignment such as a=a, always remove it
         // regardless of what the liveness results because it does not
         // change the result afterward.
         if (null !== $rhs && $rhs instanceof \PHPParser_Node_Expr_Variable && $var->getName() === $rhs->name && $n instanceof \PHPParser_Node_Expr_Assign) {
             $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.assignment_to_itself', 'Why assign ``%variable%`` to itself?', array('variable' => '$' . $rhs->name)));
             return;
         }
         // At the moment we miss some dead assignments if a variable is killed,
         // and defined in the same node of the control flow graph.
         // Example: $a = 'foo'; return $a = 'bar';
         if ($outState->isLive($var)) {
             return;
             // Variable is not dead.
         }
         // Assignments to references do not need to be used in the
         // current scope.
         if ($var->isReference()) {
             return;
         }
         //             if ($inState->isLive($var)
         //                    /*&& $this->isVariableStillLiveWithinExpression($n, $exprRoot, $var->getName()) */) {
         // The variable is killed here but it is also live before it.
         // This is possible if we have say:
         //    if ($X = $a && $a = $C) {..} ; .......; $a = $S;
         // In this case we are safe to remove "$a = $C" because it is dead.
         // However if we have:
         //    if ($a = $C && $X = $a) {..} ; .......; $a = $S;
         // removing "a = C" is NOT correct, although the live set at the node
         // is exactly the same.
         // TODO: We need more fine grain CFA or we need to keep track
         // of GEN sets when we recurse here. Maybe add the comment anyway, and let the user decide?
         //                 return;
         //             }
         if ($n instanceof \PHPParser_Node_Expr_Assign) {
             if ($n->getAttribute('is_list_assign', false)) {
                 $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.unnecessary_list_assign', 'The assignment to ``$%variable%`` is dead. Consider omitting it like so ``list($first,,$third)``.', array('variable' => $var->getName())));
                 return;
             }
             $this->phpFile->addComment($n->getLine(), Comment::warning('dead_assignment.unnecessary_var_assign', 'The assignment to ``$%variable%`` is dead and can be removed.', array('variable' => $var->getName())));
             return;
         }
     }
 }
 private function checkIfElseStatements(\PHPParser_Node_Stmt_If $node, BlockNode $if, BlockNode $else)
 {
     $ifTrue = $this->returnsTrue($if);
     $elseTrue = $this->returnsTrue($else);
     if ($ifTrue === $elseTrue) {
         if (null === $node->else) {
             $this->phpFile->addComment($node->getLine(), Comment::error('suspicious_code.superfluous_if_return', 'This ``if`` statement and the following ``return`` statement are superfluous as you return always ``%return_value%``.', array('return_value' => $ifTrue ? 'true' : 'false')));
         } else {
             $this->phpFile->addComment($node->getLine(), Comment::error('suspicious_code.superfluous_if_else', 'This ``if``-``else`` statement are superfluous as you return always ``%return_value%``.', array('return_value' => $ifTrue ? 'true' : 'false')));
         }
         return;
     }
     $condType = $node->cond->getAttribute('type');
     $possiblyNeedsCast = !$condType || !$condType->isBooleanType();
     $cond = $node->cond;
     if (!$ifTrue) {
         if ($cond instanceof \PHPParser_Node_Expr_BooleanNot) {
             $cond = $cond->expr;
             $condType = $cond->getAttribute('type');
             $possiblyNeedsCast = !$condType || !$condType->isBooleanType();
         } else {
             $cond = new \PHPParser_Node_Expr_BooleanNot($cond);
             $possiblyNeedsCast = false;
         }
     }
     if ($possiblyNeedsCast) {
         $cond = new \PHPParser_Node_Expr_Cast_Bool($cond);
     }
     if (null !== $node->else) {
         $this->phpFile->addComment($node->getLine(), Comment::warning('coding_style.simplify_boolean_return_of_if_else', 'The ``if``-``else`` statement can be simplified to ``return %expr%;``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($cond))));
     } else {
         $this->phpFile->addComment($node->getLine(), Comment::warning('coding_style.simplify_boolean_return_of_if_return', 'This ``if`` statement, and the following ``return`` statement can be replaced with ``return %expr%;``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($cond))));
     }
 }
 private function optimizeAssertFalse(\PHPParser_Node $node)
 {
     if (!isset($node->args[0])) {
         return;
     }
     $arg = $node->args[0]->value;
     $betterExpr = null;
     switch (true) {
         case $arg instanceof \PHPParser_Node_Expr_Greater:
             $betterExpr = clone $node;
             $betterExpr->name = 'assertLessThanOrEqual';
             $betterExpr->args = $this->createArgs($arg->right, $arg->left);
             break;
         case $arg instanceof \PHPParser_Node_Expr_GreaterOrEqual:
             $betterExpr = clone $node;
             $betterExpr->name = 'assertLessThan';
             $betterExpr->args = $this->createArgs($arg->right, $arg->left);
             break;
         case $arg instanceof \PHPParser_Node_Expr_Smaller:
             $betterExpr = clone $node;
             $betterExpr->name = 'assertGreaterThanOrEqual';
             $betterExpr->args = $this->createArgs($arg->left, $arg->right);
             break;
         case $arg instanceof \PHPParser_Node_Expr_SmallerOrEqual:
             $betterExpr = clone $node;
             $betterExpr->name = 'assertGreaterThan';
             $betterExpr->args = $this->createArgs($arg->left, $arg->right);
             break;
     }
     if ($betterExpr) {
         if (isset($node->args[1])) {
             $this->phpFile->addComment($node->getLine(), Comment::warning('phpunit.more_specific_assertion_for_false_with_custom_message', 'Instead of ``assertFalse()`` consider using ``%expr%``.', array('expr' => self::$prettyPrinter->prettyPrintExpr($betterExpr))));
         } else {
             $this->phpFile->addComment($node->getLine(), Comment::warning('phpunit.more_specific_assertion_for_false', 'Instead of ``assertFalse()`` use ``%expr%``. This will lead to a better error message when the test fails.', array('expr' => self::$prettyPrinter->prettyPrintExpr($betterExpr))));
         }
     }
 }
 private function checkBitwiseOperation()
 {
     if (!NodeUtil::isEqualityExpression($this->stream->node->left)) {
         return;
     }
     /*
      * When this method is called, the token stream points to the bitwise operation already.
      *
      * 0 === foo() & 5;
      *             ^
      * -----------(1)
      *
      * (1) The position of the token stream when this method is called.
      *
      * We now check that the equality expression on the left side (in the case above, it is an Identical node) ends
      * with a parenthesis, and the ending parenthesis is started somewhere on the left side side of the equality
      * operator (===).
      */
     /** @var AbstractToken $closingParenthesis */
     $closingParenthesis = $this->stream->token->findPreviousToken('NO_WHITESPACE_OR_COMMENT')->get();
     if ($closingParenthesis->matches(')')) {
         $startToken = $this->stream->node->left->left->getAttribute('start_token');
         // The left operator of the equality sign is not assigned an explicit start token, so we do not know where
         // it started. We can just bail out here, and hope that the closing parenthesis is indeed started on the left
         // side. This needs to be improved in the SimultaneousTokenAndAstStream over time so that we ideally always
         // have a start token available.
         if (null === $startToken) {
             return;
         }
         // The closing parenthesis was started somewhere on the left side, good !
         if ($closingParenthesis->isClosing($startToken->findPreviousToken('NO_WHITESPACE_OR_COMMENT')->get())) {
             return;
         }
     }
     $this->phpFile->addComment($this->stream->node->getLine(), Comment::warning('precedence.possibly_wrong_comparison_of_bitop_result', 'Consider adding parentheses for clarity. Current Interpretation: ``%current_compare%``, Probably Intended Meaning: ``%alternative_compare%``', array('current_compare' => '(' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->left) . ') ' . NodeUtil::getBitOp($this->stream->node) . ' ' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->right), 'alternative_compare' => self::$prettyPrinter->prettyPrintExpr($this->stream->node->left->left) . ' ' . NodeUtil::getEqualOp($this->stream->node->left) . ' (' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->left->right) . ' ' . NodeUtil::getBitOp($this->stream->node) . ' ' . self::$prettyPrinter->prettyPrintExpr($this->stream->node->right) . ')')));
 }
 private function createGetNameComment(\PHPParser_Node_Expr_MethodCall $node)
 {
     $propertyAccess = new \PHPParser_Node_Expr_PropertyFetch($node->var, 'name');
     return Comment::warning('reflection_usage.get_name', 'Consider using ``%property_access%``. There is [an issue](https://bugs.php.net/bug.php?id=61384) with ``getName()`` and APC-enabled PHP versions.', array('property_access' => self::$prettyPrinter->prettyPrintExpr($propertyAccess)))->varyIn(array());
 }
 private function verifyReturnType(PhpType $type, \PHPParser_Node $node)
 {
     if (null === ($commentType = $this->parser->getTypeFromReturnAnnotation($node))) {
         if ($this->getSetting('ask_for_return_if_not_inferrable') && $type->isUnknownType()) {
             $this->phpFile->addComment($node->getLine(), Comment::warning('php_doc.return_type_not_inferrable', 'Please add a ``@return`` annotation.'));
         }
         return;
     }
     if (!$this->getSetting('return')) {
         return;
     }
     if (!$type->isSubtypeOf($commentType)) {
         if ($this->getSetting('suggest_more_specific_types') && $this->typeRegistry->getNativeType('array')->isSubtypeOf($type)) {
             $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_mismatch_with_generic_array', 'Should the return type not be ``%expected_type%``? Also, consider making the array more specific, something like ``array<String>``, or ``String[]``.', array('expected_type' => $type->getDocType($this->importedNamespaces))));
         } else {
             $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_mismatch', 'Should the return type not be ``%expected_type%``?', array('expected_type' => $type->getDocType($this->importedNamespaces))));
         }
         return;
     }
     if (!$this->getSetting('suggest_more_specific_types')) {
         return;
     }
     if (!$this->isMoreSpecificType($type, $commentType)) {
         if ($type->isAllType()) {
             $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_all_type_more_specific', 'Please define a more specific return type; maybe using a union like ``null|Object``, or ``string|array``.'));
         } else {
             if ($this->typeRegistry->getNativeType('array')->isSubtypeOf($type)) {
                 $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_array_element_type', 'Please define the element type for the returned array (using ``array<SomeType>``, or ``SomeType[]``).'));
             }
         }
         return;
     }
     $this->phpFile->addComment($this->getLineOfReturn($node), Comment::warning('php_doc.return_type_more_specific', 'Consider making the return type a bit more specific; maybe use ``%actual_type%``.', array('actual_type' => $type->getDocType($this->importedNamespaces))));
 }