fromNode() 공개 정적인 메소드

public static fromNode ( Context $context, CodeBase $code_base, ast\Node | string | null $node, boolean $should_catch_issue_exception = true ) : UnionType
$context Context The context of the parser at the node for which we'd like to determine a type
$code_base Phan\CodeBase The code base within which we're operating
$node ast\Node | string | null The node for which we'd like to determine its type
$should_catch_issue_exception boolean Set to true to cause loggable issues to be thrown instead of emitted as issues to the log.
리턴 UnionType
예제 #1
0
파일: Variable.php 프로젝트: hslatman/phan
 /**
  * @param Node $node
  * An AST_VAR node
  *
  * @param Context $context
  * The context in which the variable is found
  *
  * @param CodeBase $code_base
  *
  * @return Variable
  * A variable begotten from a node
  */
 public static function fromNodeInContext(Node $node, Context $context, CodeBase $code_base, bool $should_check_type = true) : Variable
 {
     $variable_name = AST::variableName($node);
     // Get the type of the assignment
     $union_type = $should_check_type ? UnionType::fromNode($context, $code_base, $node) : new UnionType();
     $variable = new Variable($context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? 0), $variable_name, $union_type, $node->flags);
     return $variable;
 }
예제 #2
0
 /**
  * Force the future to figure out the type of the
  * given object or throw an IssueException if it
  * is unable to do so
  *
  * @return UnionType
  * The type of the future
  *
  * @throws IssueException
  * An exception is thrown if we are unable to determine
  * the type at the time this method is called
  */
 public function get() : UnionType
 {
     return UnionType::fromNode($this->context, $this->code_base, $this->node, false);
 }
예제 #3
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitForeach(Node $node) : Context
 {
     if ($node->children['value']->kind == \ast\AST_LIST) {
         foreach ($node->children['value']->children as $child_node) {
             $variable = Variable::fromNodeInContext($child_node, $this->context, $this->code_base, false);
             $this->context->addScopeVariable($variable);
         }
         // Otherwise, read the value as regular variable and
         // add it to the scope
     } else {
         // Create a variable for the value
         $variable = Variable::fromNodeInContext($node->children['value'], $this->context, $this->code_base, false);
         // Get the type of the node from the left side
         $type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
         // Filter out the non-generic types of the
         // expression
         $non_generic_type = $type->asNonGenericTypes();
         // If we were able to figure out the type and its
         // a generic type, then set its element types as
         // the type of the variable
         if (!$non_generic_type->isEmpty()) {
             $variable->setUnionType($non_generic_type);
         }
         // Add the variable to the scope
         $this->context->addScopeVariable($variable);
     }
     // If there's a key, make a variable out of that too
     if (!empty($node->children['key'])) {
         if ($node->children['key'] instanceof \ast\Node && $node->children['key']->kind == \ast\AST_LIST) {
             Log::err(Log::EFATAL, "Can't use list() as a key element - aborting", $this->context->getFile(), $node->lineno);
         }
         $variable = Variable::fromNodeInContext($node->children['key'], $this->context, $this->code_base, false);
         $this->context->addScopeVariable($variable);
     }
     // Note that we're not creating a new scope, just
     // adding variables to the existing scope
     return $this->context;
 }
예제 #4
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param Method $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @param CodeBase $code_base
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arg_check
  * Formerly `function arg_check`
  */
 private static function analyzeInternalArgumentType(Method $method, Node $node, Context $context, CodeBase $code_base)
 {
     $arglist = $node->children['args'];
     $argcount = count($arglist->children);
     switch ($method->getName()) {
         case 'join':
         case 'implode':
             // (string glue, array pieces),
             // (array pieces, string glue) or
             // (array pieces)
             if ($argcount == 1) {
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(pieces) is %s but {$method->getFQSEN()}() takes array when passed only 1 arg");
                 return;
             } else {
                 if ($argcount == 2) {
                     $arg1_type = UnionType::fromNode($context, $code_base, $arglist->children[0]);
                     $arg2_type = UnionType::fromNode($context, $code_base, $arglist->children[1]);
                     if ((string) $arg1_type == 'array') {
                         if (!$arg1_type->canCastToUnionType(StringType::instance()->asUnionType())) {
                             Log::err(Log::EPARAM, "arg#2(glue) is {$arg2_type} but {$method->getFQSEN()}() takes string when arg#1 is array", $context->getFile(), $context->getLineNumberStart());
                         }
                     } else {
                         if ((string) $arg1_type == 'string') {
                             if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                                 Log::err(Log::EPARAM, "arg#2(pieces) is {$arg2_type} but {$method->getFQSEN()}() takes array when arg#1 is string", $context->getFile(), $context->getLineNumberStart());
                             }
                         }
                     }
                     return;
                 }
             }
             // Any other arg counts we will let the regular
             // checks handle
             break;
         case 'array_udiff':
         case 'array_diff_uassoc':
         case 'array_uintersect_assoc':
         case 'array_intersect_ukey':
             if ($argcount < 3) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 return;
             }
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), "The last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 1; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, CallableType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes array");
             }
             return;
         case 'array_diff_uassoc':
         case 'array_uintersect_uassoc':
             if ($argcount < 4) {
                 Log::err(Log::EPARAM, "call with {$argcount} arg(s) to {$method->getFQSEN()}() which requires {$method->getNumberOfRequiredParameters()} arg(s)", $context->getFile(), $context->getLineNumberStart());
                 return;
             }
             // The last 2 arguments must be a callable and there
             // can be a variable number of arrays before it
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), "The last argument to {$method->getFQSEN()} must be a callable");
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 2], $context, $code_base, CallableType::instance()->asUnionType(), "The second last argument to {$method->getFQSEN()} must be a callable");
             for ($i = 0; $i < $argcount - 2; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#" . ($i + 1) . " is %s but {$method->getFQSEN()}() takes array");
             }
             return;
         case 'strtok':
             // (string str, string token) or (string token)
             if ($argcount == 1) {
                 // If we have just one arg it must be a string token
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(token) is %s but {$method->getFQSEN()}() takes string when passed only one arg");
             }
             // The arginfo check will handle the other case
             break;
         case 'min':
         case 'max':
             if ($argcount == 1) {
                 // If we have just one arg it must be an array
                 if (!self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), "arg#1(values) is %s but {$method->getFQSEN()}() takes array when passed only one arg")) {
                     return;
                 }
             }
             // The arginfo check will handle the other case
             break;
         default:
             break;
     }
 }
예제 #5
0
 /**
  * Visit a node with kind `\ast\AST_METHOD_CALL`
  *
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return UnionType
  * The set of types that are possibly produced by the
  * given node
  */
 public function visitMethodCall(Node $node) : UnionType
 {
     $method_name = $node->children['method'] ?? '';
     // Give up on any complicated nonsense where the
     // method name is a variable such as in
     // `$variable->$function_name()`.
     if ($method_name instanceof Node) {
         return new UnionType();
     }
     // Method names can some times turn up being
     // other method calls.
     assert(is_string($method_name), "Method name must be a string. Something else given.");
     try {
         $class_fqsen = null;
         foreach ($this->classListFromNode($node->children['class'] ?? $node->children['expr']) as $i => $class) {
             $class_fqsen = $class->getFQSEN();
             if (!$class->hasMethodWithName($this->code_base, $method_name)) {
                 continue;
             }
             try {
                 $method = $class->getMethodByNameInContext($this->code_base, $method_name, $this->context);
                 $union_type = $method->getUnionType();
                 // Map template types to concrete types
                 if ($union_type->hasTemplateType()) {
                     // Get the type of the object calling the property
                     $expression_type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
                     // Map template types to concrete types
                     $union_type = $union_type->withTemplateParameterTypeMap($expression_type->getTemplateParameterTypeMap($this->code_base));
                 }
                 // Remove any references to \static or \static[]
                 // once we're talking about the method's return
                 // type outside of its class
                 if ($union_type->hasStaticType()) {
                     $union_type = clone $union_type;
                     $union_type->removeType(\Phan\Language\Type\StaticType::instance());
                 }
                 if ($union_type->genericArrayElementTypes()->hasStaticType()) {
                     $union_type = clone $union_type;
                     // Find the static type on the list
                     $static_type = $union_type->getTypeSet()->find(function (Type $type) : bool {
                         return $type->isGenericArray() && $type->genericArrayElementType()->isStaticType();
                     });
                     // Remove it from the list
                     $union_type->removeType($static_type);
                 }
                 return $union_type;
             } catch (IssueException $exception) {
                 return new UnionType();
             }
         }
     } catch (IssueException $exception) {
         // Swallow it
     } catch (CodeBaseException $exception) {
         $this->emitIssue(Issue::UndeclaredClassMethod, $node->lineno ?? 0, $method_name, (string) $exception->getFQSEN());
     }
     return new UnionType();
 }
예제 #6
0
 /**
  * Analyze the parameters and arguments for a call
  * to the given method or function
  *
  * @param CodeBase $code_base
  * @param Method $method
  * @param Node $node
  *
  * @return null
  */
 private function analyzeCallToMethod(CodeBase $code_base, Method $method, Node $node)
 {
     if (Database::isEnabled()) {
         // Store the call to the method so we can track
         // dependencies later
         (new CalledBy((string) $method->getFQSEN(), $this->context))->write(Database::get());
     }
     // Create variables for any pass-by-reference
     // parameters
     $argument_list = $node->children['args'];
     foreach ($argument_list->children as $i => $argument) {
         $parameter = $method->getParameterList()[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If pass-by-reference, make sure the variable exists
         // or create it if it doesn't.
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 // We don't do anything with it; just create it
                 // if it doesn't exist
                 $variable = AST::getOrCreateVariableFromNodeInContext($argument, $this->context, $this->code_base);
             } else {
                 if ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                     $property_name = $argument->children['prop'];
                     if (is_string($property_name)) {
                         // We don't do anything with it; just create it
                         // if it doesn't exist
                         try {
                             $property = AST::getOrCreatePropertyFromNodeInContext($argument->children['prop'], $argument, $this->context, $this->code_base);
                         } catch (CodeBaseException $exception) {
                             Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                         } catch (NodeException $exception) {
                             // If we can't figure out what kind of a call
                             // this is, don't worry about it
                         }
                     } else {
                         // This is stuff like `Class->$foo`. I'm ignoring
                         // it.
                     }
                 }
             }
         }
     }
     // Confirm the argument types are clean
     ArgumentType::analyze($method, $node, $this->context, $this->code_base);
     // Take another pass over pass-by-reference parameters
     // and assign types to passed in variables
     foreach ($argument_list->children as $i => $argument) {
         $parameter = $method->getParameterList()[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If the parameter is pass-by-reference and we're
         // passing a variable in, see if we should pass
         // the parameter and variable types to eachother
         $variable = null;
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 $variable = AST::getOrCreateVariableFromNodeInContext($argument, $this->context, $this->code_base);
             } else {
                 if ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                     $property_name = $argument->children['prop'];
                     if (is_string($property_name)) {
                         // We don't do anything with it; just create it
                         // if it doesn't exist
                         try {
                             $variable = AST::getOrCreatePropertyFromNodeInContext($argument->children['prop'], $argument, $this->context, $this->code_base);
                         } catch (CodeBaseException $exception) {
                             Log::err(Log::EUNDEF, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                         } catch (NodeException $exception) {
                             // If we can't figure out what kind of a call
                             // this is, don't worry about it
                         }
                     } else {
                         // This is stuff like `Class->$foo`. I'm ignoring
                         // it.
                     }
                 }
             }
             if ($variable) {
                 $variable->getUnionType()->addUnionType($parameter->getUnionType());
             }
         }
     }
     // If we're in quick mode, don't retest methods based on
     // parameter types passed in
     if (Config::get()->quick_mode) {
         return;
     }
     // We're going to hunt to see if any of the arguments
     // have a mismatch with the parameters. If so, we'll
     // re-check the method to see how the parameters impact
     // its return type
     $has_argument_parameter_mismatch = false;
     // Now that we've made sure the arguments are sufficient
     // for definitions on the method, we iterate over the
     // arguments again and add their types to the parameter
     // types so we can test the method again
     $argument_list = $node->children['args'];
     // We create a copy of the parameter list so we can switch
     // back to it after
     $original_parameter_list = $method->getParameterList();
     foreach ($argument_list->children as $i => $argument) {
         $parameter = $method->getParameterList()[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If the parameter has no type, pass the
         // argument's type to it
         if ($parameter->getUnionType()->isEmpty()) {
             $has_argument_parameter_mismatch = true;
             $argument_type = UnionType::fromNode($this->context, $this->code_base, $argument);
             // If this isn't an internal function or method
             // and it has no type, add the argument's type
             // to it so we can compare it to subsequent
             // calls
             if (!$parameter->getContext()->isInternal()) {
                 // Clone the parameter in the original
                 // parameter list so we can reset it
                 // later
                 $original_parameter_list[$i] = clone $parameter;
                 // Then set the new type on that parameter based
                 // on the argument's type. We'll use this to
                 // retest the method with the passed in types
                 $parameter->getUnionType()->addUnionType($argument_type);
             }
         }
     }
     // Now that we know something about the parameters used
     // to call the method, we can reanalyze the method with
     // the types of the parameter, making sure we don't get
     // into an infinite loop of checking calls to the current
     // method in scope
     if ($has_argument_parameter_mismatch && !$method->getContext()->isInternal() && (!$this->context->isMethodScope() || $method->getFQSEN() !== $this->context->getMethodFQSEN())) {
         $method->analyze($method->getContext(), $code_base);
     }
     // Reset to the original parameter list after having
     // tested the parameters with the types passed in
     $method->setParameterList($original_parameter_list);
 }
예제 #7
0
 /**
  * Visit a node with kind `\ast\AST_PROP_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitPropDecl(Node $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     // Get a comment on the property declaration
     $comment = Comment::fromStringInContext($node->children[0]->docComment ?? '', $this->context);
     foreach ($node->children ?? [] as $i => $child_node) {
         // Ignore children which are not property elements
         if (!$child_node || $child_node->kind != \ast\AST_PROP_ELEM) {
             continue;
         }
         // If something goes wrong will getting the type of
         // a property, we'll store it as a future union
         // type and try to figure it out later
         $future_union_type = null;
         try {
             // Get the type of the default
             $union_type = UnionType::fromNode($this->context, $this->code_base, $child_node->children['default'], false);
         } catch (IssueException $exception) {
             $future_union_type = new FutureUnionType($this->code_base, $this->context, $child_node->children['default']);
             $union_type = new UnionType();
         }
         // Don't set 'null' as the type if thats the default
         // given that its the default default.
         if ($union_type->isType(NullType::instance())) {
             $union_type = new UnionType();
         }
         $property_name = $child_node->children['name'];
         assert(is_string($property_name), 'Property name must be a string. ' . 'Got ' . print_r($property_name, true) . ' at ' . $this->context);
         $property = new Property(clone $this->context->withLineNumberStart($child_node->lineno ?? 0), is_string($child_node->children['name']) ? $child_node->children['name'] : '_error_', $union_type, $node->flags ?? 0);
         $property->setFQSEN(FullyQualifiedPropertyName::make($clazz->getFQSEN(), $property->getName()));
         // Add the property to the class
         $clazz->addProperty($this->code_base, $property);
         $property->setSuppressIssueList($comment->getSuppressIssueList());
         // Look for any @var declarations
         if ($variable = $comment->getVariableList()[$i] ?? null) {
             if ((string) $union_type != 'null' && !$union_type->canCastToUnionType($variable->getUnionType())) {
                 $this->emitIssue(Issue::TypeMismatchProperty, $child_node->lineno ?? 0, (string) $union_type, (string) $property->getFQSEN(), (string) $variable->getUnionType());
             }
             // Set the declared type to the doc-comment type and add
             // |null if the default value is null
             $property->getUnionType()->addUnionType($variable->getUnionType());
         }
         // Wait until after we've added the (at)var type
         // before setting the future so that calling
         // $property->getUnionType() doesn't force the
         // future to be reified.
         if (!empty($future_union_type)) {
             $property->setFutureUnionType($future_union_type);
         }
     }
     return $this->context;
 }
예제 #8
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitInstanceof(Node $node) : Context
 {
     // Only look at things of the form
     // `$variable instanceof ClassName`
     if ($node->children['expr']->kind !== \ast\AST_VAR) {
         return $this->context;
     }
     $context = $this->context;
     try {
         // Get the variable we're operating on
         $variable = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getVariable();
         // Get the type that we're checking it against
         $type = UnionType::fromNode($this->context, $this->code_base, $node->children['class']);
         // Make a copy of the variable
         $variable = clone $variable;
         // Add the type to the variable
         $variable->getUnionType()->addUnionType($type);
         // Overwrite the variable with its new type
         $context = $context->withScopeVariable($variable);
     } catch (\Exception $exception) {
         // Swallow it
     }
     return $context;
 }
예제 #9
0
 /**
  * Check to see if the given Clazz is a duplicate
  *
  * @param Method $method
  * The method we're analyzing arguments for
  *
  * @param Node $node
  * The node holding the method call we're looking at
  *
  * @param Context $context
  * The context in which we see the call
  *
  * @param CodeBase $code_base
  *
  * @return null
  *
  * @see \Phan\Deprecated\Pass2::arg_check
  * Formerly `function arg_check`
  */
 private static function analyzeInternalArgumentType(Method $method, Node $node, Context $context, CodeBase $code_base)
 {
     $arglist = $node->children['args'];
     $argcount = count($arglist->children);
     switch ($method->getName()) {
         case 'join':
         case 'implode':
             // (string glue, array pieces),
             // (array pieces, string glue) or
             // (array pieces)
             if ($argcount == 1) {
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(pieces) is %s but {$method->getFQSEN()}() takes array when passed only 1 arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'pieces', (string) $method->getFQSEN(), 'string', 'array']);
                 });
                 return;
             } else {
                 if ($argcount == 2) {
                     $arg1_type = UnionType::fromNode($context, $code_base, $arglist->children[0]);
                     $arg2_type = UnionType::fromNode($context, $code_base, $arglist->children[1]);
                     if ((string) $arg1_type == 'array') {
                         if (!$arg1_type->canCastToUnionType(StringType::instance()->asUnionType())) {
                             Issue::emit(Issue::ParamSpecial1, $context->getFile(), $context->getLineNumberStart(), 2, 'glue', (string) $arg2_type, (string) $method->getFQSEN(), 'string', 1, 'array');
                         }
                     } else {
                         if ((string) $arg1_type == 'string') {
                             if (!$arg2_type->canCastToUnionType(ArrayType::instance()->asUnionType())) {
                                 Issue::emit(Issue::ParamSpecial1, $context->getFile(), $context->getLineNumberStart(), 2, 'pieces', (string) $arg2_type, (string) $method->getFQSEN(), 'array', 1, 'string');
                             }
                         }
                     }
                     return;
                 }
             }
             // Any other arg counts we will let the regular
             // checks handle
             break;
         case 'array_udiff':
         case 'array_diff_uassoc':
         case 'array_uintersect_assoc':
         case 'array_intersect_ukey':
             if ($argcount < 3) {
                 Issue::emit(Issue::ParamTooFewInternal, $context->getFile(), $context->getLineNumberStart(), $argcount, (string) $method->getFQSEN(), $method->getNumberOfRequiredParameters());
                 return;
             }
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial3)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             for ($i = 0; $i < $argcount - 1; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method, $i) {
                     // "arg#".($i+1)." is %s but {$method->getFQSEN()}() takes array"
                     return Issue::fromType(Issue::ParamTypeMismatch)($context->getFile(), $context->getLineNumberStart(), [$i + 1, (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 });
             }
             return;
         case 'array_diff_uassoc':
         case 'array_uintersect_uassoc':
             if ($argcount < 4) {
                 Issue::emit(Issue::ParamTooFewInternal, $context->getFile(), $context->getLineNumberStart(), $argcount, (string) $method->getFQSEN(), $method->getNumberOfRequiredParameters());
                 return;
             }
             // The last 2 arguments must be a callable and there
             // can be a variable number of arrays before it
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 1], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial3)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             self::analyzeNodeUnionTypeCast($arglist->children[$argcount - 2], $context, $code_base, CallableType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                 // "The second last argument to {$method->getFQSEN()} must be a callable"
                 return Issue::fromType(Issue::ParamSpecial4)($context->getFile(), $context->getLineNumberStart(), [(string) $method->getFQSEN(), 'callable']);
             });
             for ($i = 0; $i < $argcount - 2; $i++) {
                 self::analyzeNodeUnionTypeCast($arglist->children[$i], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method, $i) {
                     // "arg#".($i+1)." is %s but {$method->getFQSEN()}() takes array"
                     return Issue::fromType(Issue::ParamTypeMismatch)($context->getFile(), $context->getLineNumberStart(), [$i + 1, (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 });
             }
             return;
         case 'strtok':
             // (string str, string token) or (string token)
             if ($argcount == 1) {
                 // If we have just one arg it must be a string token
                 self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(token) is %s but {$method->getFQSEN()}() takes string when passed only one arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'token', (string) $node_type, (string) $method->getFQSEN(), 'string']);
                 });
             }
             // The arginfo check will handle the other case
             break;
         case 'min':
         case 'max':
             if ($argcount == 1) {
                 // If we have just one arg it must be an array
                 if (!self::analyzeNodeUnionTypeCast($arglist->children[0], $context, $code_base, ArrayType::instance()->asUnionType(), function (UnionType $node_type) use($context, $method) {
                     // "arg#1(values) is %s but {$method->getFQSEN()}() takes array when passed only one arg"
                     return Issue::fromType(Issue::ParamSpecial2)($context->getFile(), $context->getLineNumberStart(), [1, 'values', (string) $node_type, (string) $method->getFQSEN(), 'array']);
                 })) {
                     return;
                 }
             }
             // The arginfo check will handle the other case
             break;
         default:
             break;
     }
 }
예제 #10
0
 /**
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return string
  * The class name represented by the given call
  */
 public function visitMethodCall(Node $node) : string
 {
     if ($node->children['expr']->kind == \ast\AST_VAR) {
         if ($node->children['expr']->children['name'] instanceof Node) {
             return '';
         }
         // $var->method()
         if ($node->children['expr']->children['name'] == 'this') {
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             return (string) $this->context->getClassFQSEN();
         }
         $variable_name = $node->children['expr']->children['name'];
         if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
             // Got lost, couldn't find the variable in the current scope
             // If it really isn't defined, it will be caught by the
             // undefined var error
             return '';
         }
         $variable = $this->context->getScope()->getVariableWithName($variable_name);
         // Hack - loop through the possible types of the var and assume
         // first found class is correct
         foreach ($variable->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
             $child_class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             if ($this->code_base->hasClassWithFQSEN($child_class_fqsen)) {
                 return (string) FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             }
         }
         // Could not find name
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_PROP) {
         $prop = $node->children['expr'];
         if (!($prop->children['expr']->kind == \ast\AST_VAR && !$prop->children['expr']->children['name'] instanceof Node)) {
             return '';
         }
         // $var->prop->method()
         $var = $prop->children['expr'];
         if ($var->children['name'] == 'this') {
             // If we're not in a class scope, 'this' won't work
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             // Get the class in scope
             $clazz = $this->code_base->getClassByFQSEN($this->context->getClassFQSEN());
             if ($prop->children['prop'] instanceof Node) {
                 // $this->$prop->method() - too dynamic, give up
                 return '';
             }
             $property_name = $prop->children['prop'];
             if ($clazz->hasPropertyWithName($this->code_base, $property_name)) {
                 try {
                     $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
                 } catch (AccessException $exception) {
                     Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                     return '';
                 }
                 // Find the first viable property type
                 foreach ($property->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
                     $class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
                     if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
                         return (string) $class_fqsen;
                     }
                 }
             }
             // No such property was found, or none were classes
             // that could be found
             return '';
         }
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_METHOD_CALL) {
         // Get the type returned by the first method
         // call.
         $union_type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
         // Find the subset of types that are viable
         // classes
         $viable_class_types = $union_type->nonNativeTypes()->nonGenericArrayTypes();
         // If there are no non-native types, give up
         if ($viable_class_types->isEmpty()) {
             return '';
         }
         // Return the first non-native type in the
         // list and hope its a class
         return (string) $viable_class_types->head();
     }
     return '';
 }
예제 #11
0
 /**
  * Visit a node with kind `\ast\AST_UNARY_OP`
  *
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return UnionType
  * The set of types that are possibly produced by the
  * given node
  */
 public function visitUnaryOp(Node $node) : UnionType
 {
     // Shortcut some easy operators
     switch ($node->flags) {
         case \ast\flags\UNARY_BOOL_NOT:
             return BoolType::instance()->asUnionType();
     }
     return UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
 }
예제 #12
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitIfElem(Node $node) : Context
 {
     if (!isset($node->children['cond']) || !$node->children['cond'] instanceof Node) {
         return $this->context;
     }
     // Get the type just to make sure everything
     // is defined.
     $expression_type = UnionType::fromNode($this->context, $this->code_base, $node->children['cond']);
     // Look to see if any proofs we do within the condition
     // can say anything about types within the statement
     // list.
     return (new ConditionVisitor($this->code_base, $this->context))($node->children['cond']);
 }
예제 #13
0
 /**
  * Analyze the parameters and arguments for a call
  * to the given method or function
  *
  * @param CodeBase $code_base
  * @param Method $method
  * @param Node $node
  *
  * @return null
  */
 private function analyzeCallToMethod(CodeBase $code_base, FunctionInterface $method, Node $node)
 {
     $method->addReference($this->context);
     // Create variables for any pass-by-reference
     // parameters
     $argument_list = $node->children['args'];
     foreach ($argument_list->children as $i => $argument) {
         if (!is_object($argument)) {
             continue;
         }
         $parameter = $method->getParameterForCaller($i);
         if (!$parameter) {
             continue;
         }
         // If pass-by-reference, make sure the variable exists
         // or create it if it doesn't.
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 // We don't do anything with it; just create it
                 // if it doesn't exist
                 $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
             } elseif ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                 $property_name = $argument->children['prop'];
                 if (is_string($property_name)) {
                     // We don't do anything with it; just create it
                     // if it doesn't exist
                     try {
                         $property = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop']);
                     } catch (IssueException $exception) {
                         Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
                     } catch (\Exception $exception) {
                         // If we can't figure out what kind of a call
                         // this is, don't worry about it
                     }
                 } else {
                     // This is stuff like `Class->$foo`. I'm ignoring
                     // it.
                 }
             }
         }
     }
     // Confirm the argument types are clean
     ArgumentType::analyze($method, $node, $this->context, $this->code_base);
     // Take another pass over pass-by-reference parameters
     // and assign types to passed in variables
     foreach ($argument_list->children as $i => $argument) {
         if (!is_object($argument)) {
             continue;
         }
         $parameter = $method->getParameterForCaller($i);
         if (!$parameter) {
             continue;
         }
         if (Config::get()->dead_code_detection) {
             (new ArgumentVisitor($this->code_base, $this->context))($argument);
         }
         // If the parameter is pass-by-reference and we're
         // passing a variable in, see if we should pass
         // the parameter and variable types to eachother
         $variable = null;
         if ($parameter->isPassByReference()) {
             if ($argument->kind == \ast\AST_VAR) {
                 $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
             } elseif ($argument->kind == \ast\AST_STATIC_PROP || $argument->kind == \ast\AST_PROP) {
                 $property_name = $argument->children['prop'];
                 if (is_string($property_name)) {
                     // We don't do anything with it; just create it
                     // if it doesn't exist
                     try {
                         $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop']);
                     } catch (IssueException $exception) {
                         Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
                     } catch (\Exception $exception) {
                         // If we can't figure out what kind of a call
                         // this is, don't worry about it
                     }
                 } else {
                     // This is stuff like `Class->$foo`. I'm ignoring
                     // it.
                 }
             }
             if ($variable) {
                 $variable->getUnionType()->addUnionType($parameter->getVariadicElementUnionType());
             }
         }
     }
     // If we're in quick mode, don't retest methods based on
     // parameter types passed in
     if (Config::get()->quick_mode) {
         return;
     }
     // We're going to hunt to see if any of the arguments
     // have a mismatch with the parameters. If so, we'll
     // re-check the method to see how the parameters impact
     // its return type
     $has_argument_parameter_mismatch = false;
     // Now that we've made sure the arguments are sufficient
     // for definitions on the method, we iterate over the
     // arguments again and add their types to the parameter
     // types so we can test the method again
     $argument_list = $node->children['args'];
     // We create a copy of the parameter list so we can switch
     // back to it after
     $original_parameter_list = $method->getParameterList();
     // Create a backup of the method's scope so that we can
     // reset it after f*****g with it below
     $original_method_scope = $method->getInternalScope();
     foreach ($argument_list->children as $i => $argument) {
         // TODO(Issue #376): Support inference on the child in **the set of vargs**, not just the first vararg
         // This is just testing the first vararg.
         // The implementer will also need to restore the original parameter list.
         $parameter = $original_parameter_list[$i] ?? null;
         if (!$parameter) {
             continue;
         }
         // If the parameter has no type, pass the
         // argument's type to it
         if ($parameter->getVariadicElementUnionType()->isEmpty()) {
             $has_argument_parameter_mismatch = true;
             // If this isn't an internal function or method
             // and it has no type, add the argument's type
             // to it so we can compare it to subsequent
             // calls
             if (!$parameter->isInternal()) {
                 $argument_type = UnionType::fromNode($this->context, $this->code_base, $argument);
                 // Clone the parameter in the original
                 // parameter list so we can reset it
                 // later
                 // TODO: If there are varargs and this is beyond the end, ensure last arg is cloned.
                 $original_parameter_list[$i] = clone $original_parameter_list[$i];
                 // Then set the new type on that parameter based
                 // on the argument's type. We'll use this to
                 // retest the method with the passed in types
                 $parameter->getVariadicElementUnionType()->addUnionType($argument_type);
                 if (!is_object($argument)) {
                     continue;
                 }
                 // If we're passing by reference, get the variable
                 // we're dealing with wrapped up and shoved into
                 // the scope of the method
                 if ($parameter->isPassByReference()) {
                     if ($original_parameter_list[$i]->isVariadic()) {
                         // For now, give up and work on it later.
                         // TODO(Issue #376): It's possible to have a parameter `&...$args`. Analysing that is going to be a problem.
                         // Is it possible to create `PassByReferenceVariableCollection extends Variable` or something similar?
                     } elseif ($argument->kind == \ast\AST_VAR) {
                         // Get the variable
                         $variable = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateVariable();
                         $pass_by_reference_variable = new PassByReferenceVariable($parameter, $variable);
                         $parameter_list = $method->getParameterList();
                         $parameter_list[$i] = $pass_by_reference_variable;
                         $method->setParameterList($parameter_list);
                         // Add it to the scope of the function wrapped
                         // in a way that makes it addressable as the
                         // parameter its mimicking
                         $method->getInternalScope()->addVariable($pass_by_reference_variable);
                     } else {
                         if ($argument->kind == \ast\AST_STATIC_PROP) {
                             // Get the variable
                             $property = (new ContextNode($this->code_base, $this->context, $argument))->getOrCreateProperty($argument->children['prop'] ?? '');
                             $pass_by_reference_variable = new PassByReferenceVariable($parameter, $property);
                             $parameter_list = $method->getParameterList();
                             $parameter_list[$i] = $pass_by_reference_variable;
                             $method->setParameterList($parameter_list);
                             // Add it to the scope of the function wrapped
                             // in a way that makes it addressable as the
                             // parameter its mimicking
                             $method->getInternalScope()->addVariable($pass_by_reference_variable);
                         }
                     }
                 } else {
                     // Overwrite the method's variable representation
                     // of the parameter with the parameter with the
                     // new type
                     $method->getInternalScope()->addVariable($parameter);
                 }
             }
         }
     }
     // Now that we know something about the parameters used
     // to call the method, we can reanalyze the method with
     // the types of the parameter, making sure we don't get
     // into an infinite loop of checking calls to the current
     // method in scope
     if ($has_argument_parameter_mismatch && !$method->isInternal() && (!$this->context->isInFunctionLikeScope() || $method->getFQSEN() !== $this->context->getFunctionLikeFQSEN())) {
         $method->analyze($method->getContext(), $code_base);
     }
     // Reset to the original parameter list after having
     // tested the parameters with the types passed in
     $method->setParameterList($original_parameter_list);
     // Reset the scope to its original version before we
     // put new parameters in it
     $method->setInternalScope($original_method_scope);
 }
예제 #14
0
 /**
  * Visit a node with kind `\ast\AST_CLASS_CONST_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitClassConstDecl(Node $node) : Context
 {
     $clazz = $this->getContextClass();
     foreach ($node->children ?? [] as $child_node) {
         $constant = new Constant($this->context->withLineNumberStart($child_node->lineno ?? 0)->withLineNumberEnd($child_node->endLineno ?? 0), $child_node->children['name'], UnionType::fromNode($this->context, $this->code_base, $child_node->children['value']), $child_node->flags ?? 0);
         $clazz->addConstant($this->code_base, $constant);
     }
     return $this->context;
 }
예제 #15
0
 /**
  * Visit a node with kind `\ast\AST_ARRAY`
  *
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return UnionType
  * The set of types that are possibly produced by the
  * given node
  */
 public function visitArray(Node $node) : UnionType
 {
     if (!empty($node->children) && $node->children[0] instanceof Node && $node->children[0]->kind == \ast\AST_ARRAY_ELEM) {
         $element_types = [];
         // Check the first 5 (completely arbitrary) elements
         // and assume the rest are the same type
         for ($i = 0; $i < 5; $i++) {
             // Check to see if we're out of elements
             if (empty($node->children[$i])) {
                 break;
             }
             if ($node->children[$i]->children['value'] instanceof Node) {
                 $element_types[] = UnionType::fromNode($this->context, $this->code_base, $node->children[$i]->children['value']);
             } else {
                 $element_types[] = Type::fromObject($node->children[$i]->children['value'])->asUnionType();
             }
         }
         $element_types = array_values(array_unique($element_types));
         if (count($element_types) == 1) {
             return $element_types[0]->asGenericArrayTypes();
         }
     }
     return ArrayType::instance()->asUnionType();
 }
예제 #16
0
 /**
  * @return Parameter
  * A parameter built from a node
  *
  * @see \Phan\Deprecated\Pass1::node_param
  * Formerly `function node_param`
  */
 public static function fromNode(Context $context, CodeBase $code_base, Node $node) : Parameter
 {
     assert($node instanceof Node, "node was not an \\ast\\Node");
     // Get the type of the parameter
     $union_type = UnionType::fromNode($context, $code_base, $node->children['type']);
     // Create the skeleton parameter from what we know so far
     $parameter = new Parameter($context, (string) $node->children['name'], $union_type, $node->flags ?? 0);
     // If there is a default value, store it and its type
     if (($default_node = $node->children['default']) !== null) {
         // We can't figure out default values during the
         // parsing phase, unfortunately
         if (!$default_node instanceof Node || $default_node->kind == \ast\AST_CONST || $default_node->kind == \ast\AST_UNARY_OP || $default_node->kind == \ast\AST_ARRAY) {
             // Get the type of the default
             $union_type = UnionType::fromNode($context, $code_base, $default_node);
             // Set the default value
             $parameter->setDefaultValueType($union_type);
         } else {
             try {
                 // Get the type of the default
                 $union_type = UnionType::fromNode($context, $code_base, $default_node, false);
             } catch (IssueException $exception) {
                 // If we're in the parsing phase and we
                 // depend on a constant that isn't yet
                 // defined, give up and set it to
                 // bool|float|int|string to avoid having
                 // to handle a future type.
                 $union_type = new UnionType([BoolType::instance(), FloatType::instance(), IntType::instance(), StringType::instance()]);
             }
             // Set the default value
             $parameter->setDefaultValueType($union_type);
         }
     }
     return $parameter;
 }
예제 #17
0
 /**
  * @return string
  * A string representation of the union type begotten from
  * the first statement in the statement list in the given
  * code.
  */
 private function typeStringFromCode(string $code) : string
 {
     return UnionType::fromNode($this->context, $this->code_base, \ast\parse_code($code, Config::get()->ast_version)->children[0])->asExpandedTypes($this->code_base)->__toString();
 }
예제 #18
0
파일: Parameter.php 프로젝트: jazzdan/phan
 /**
  * @return Parameter
  * A parameter built from a node
  *
  * @see \Phan\Deprecated\Pass1::node_param
  * Formerly `function node_param`
  */
 public static function fromNode(Context $context, CodeBase $code_base, Node $node) : Parameter
 {
     assert($node instanceof Node, "node was not an \\ast\\Node");
     // Get the type of the parameter
     $type = UnionType::fromSimpleNode($context, $node->children['type']);
     $comment = Comment::fromStringInContext($node->docComment ?? '', $context);
     // Create the skeleton parameter from what we know so far
     $parameter = new Parameter($context, (string) $node->children['name'], $type, $node->flags ?? 0);
     // If there is a default value, store it and its type
     if (($default_node = $node->children['default']) !== null) {
         // We can't figure out default values during the
         // parsing phase, unfortunately
         if (!$default_node instanceof Node || $default_node->kind == \ast\AST_CONST || $default_node->kind == \ast\AST_UNARY_OP || $default_node->kind == \ast\AST_ARRAY) {
             // Set the default value
             $parameter->setDefaultValue($node->children['default'], UnionType::fromNode($context, $code_base, $node->children['default']));
         } else {
             // Nodes here may be of type \ast\AST_CLASS_CONST
             // which we can't figure out during the first
             // parsing pass
             $parameter->setDefaultValue(null, NullType::instance()->asUnionType());
         }
     }
     return $parameter;
 }
예제 #19
0
파일: Method.php 프로젝트: tpunt/phan
 /**
  * @param Context $context
  * The context in which the node appears
  *
  * @param CodeBase $code_base
  *
  * @param Node $node
  * An AST node representing a method
  *
  * @return Method
  * A Method representing the AST node in the
  * given context
  */
 public static function fromNode(Context $context, CodeBase $code_base, Decl $node, FullyQualifiedMethodName $fqsen) : Method
 {
     // Create the skeleton method object from what
     // we know so far
     $method = new Method($context, (string) $node->name, new UnionType(), $node->flags ?? 0, $fqsen);
     // Parse the comment above the method to get
     // extra meta information about the method.
     $comment = Comment::fromStringInContext($node->docComment ?? '', $context);
     // @var Parameter[]
     // The list of parameters specified on the
     // method
     $parameter_list = Parameter::listFromNode($context, $code_base, $node->children['params']);
     // Add each parameter to the scope of the function
     foreach ($parameter_list as $parameter) {
         $method->getInternalScope()->addVariable($parameter);
     }
     // If the method is Analyzable, set the node so that
     // we can come back to it whenever we like and
     // rescan it
     $method->setNode($node);
     // Set the parameter list on the method
     $method->setParameterList($parameter_list);
     $method->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isRequired() ? 1 : 0);
     }, 0));
     $method->setNumberOfOptionalParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isOptional() ? 1 : 0);
     }, 0));
     // Check to see if the comment specifies that the
     // method is deprecated
     $method->setIsDeprecated($comment->isDeprecated());
     $method->setSuppressIssueList($comment->getSuppressIssueList());
     if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) {
         $method->setNumberOfOptionalParameters(999);
         $method->setNumberOfRequiredParameters(0);
     }
     // Take a look at method return types
     if ($node->children['returnType'] !== null) {
         // Get the type of the parameter
         $union_type = UnionType::fromNode($context, $code_base, $node->children['returnType']);
         $method->getUnionType()->addUnionType($union_type);
     }
     if ($comment->hasReturnUnionType()) {
         // See if we have a return type specified in the comment
         $union_type = $comment->getReturnType();
         if ($union_type->hasSelfType()) {
             // We can't actually figure out 'static' at this
             // point, but fill it in regardless. It will be partially
             // correct
             if ($context->isInClassScope()) {
                 // n.b.: We're leaving the reference to self, static
                 //       or $this in the type because I'm guessing
                 //       it doesn't really matter. Apologies if it
                 //       ends up being an issue.
                 $union_type->addUnionType($context->getClassFQSEN()->asUnionType());
             }
         }
         $method->getUnionType()->addUnionType($union_type);
     }
     // Add params to local scope for user functions
     if (!$method->isInternal()) {
         $parameter_offset = 0;
         foreach ($method->getParameterList() as $i => $parameter) {
             if ($parameter->getUnionType()->isEmpty()) {
                 // If there is no type specified in PHP, check
                 // for a docComment with @param declarations. We
                 // assume order in the docComment matches the
                 // parameter order in the code
                 if ($comment->hasParameterWithNameOrOffset($parameter->getName(), $parameter_offset)) {
                     $comment_type = $comment->getParameterWithNameOrOffset($parameter->getName(), $parameter_offset)->getUnionType();
                     $parameter->getUnionType()->addUnionType($comment_type);
                 }
             }
             // If there's a default value on the parameter, check to
             // see if the type of the default is cool with the
             // specified type.
             if ($parameter->hasDefaultValue()) {
                 $default_type = $parameter->getDefaultValueType();
                 if (!$default_type->isEqualTo(NullType::instance()->asUnionType())) {
                     if (!$default_type->isEqualTo(NullType::instance()->asUnionType()) && !$default_type->canCastToUnionType($parameter->getUnionType())) {
                         Issue::maybeEmit($code_base, $context, Issue::TypeMismatchDefault, $node->lineno ?? 0, (string) $parameter->getUnionType(), $parameter->getName(), (string) $default_type);
                     }
                     $parameter->getUnionType()->addUnionType($default_type);
                 }
                 // If we have no other type info about a parameter,
                 // just because it has a default value of null
                 // doesn't mean that is its type. Any type can default
                 // to null
                 if ((string) $default_type === 'null' && !$parameter->getUnionType()->isEmpty()) {
                     $parameter->getUnionType()->addType(NullType::instance());
                 }
             }
             ++$parameter_offset;
         }
     }
     return $method;
 }
예제 #20
0
파일: Func.php 프로젝트: ablyler/phan
 /**
  * @param Context $context
  * The context in which the node appears
  *
  * @param CodeBase $code_base
  *
  * @param Node $node
  * An AST node representing a function
  *
  * @return Func
  * A Func representing the AST node in the
  * given context
  */
 public static function fromNode(Context $context, CodeBase $code_base, Decl $node) : Func
 {
     // Parse the comment above the function to get
     // extra meta information about the function.
     $comment = Comment::fromStringInContext($node->docComment ?? '', $context);
     // @var Parameter[]
     // The list of parameters specified on the
     // function
     $parameter_list = Parameter::listFromNode($context, $code_base, $node->children['params']);
     // Add each parameter to the scope of the function
     foreach ($parameter_list as $parameter) {
         $context = $context->withScopeVariable($parameter);
     }
     // Create the skeleton function object from what
     // we know so far
     $func = new Func($context, (string) $node->name, new UnionType(), $node->flags ?? 0);
     // If the function is Analyzable, set the node so that
     // we can come back to it whenever we like and
     // rescan it
     $func->setNode($node);
     // Set the parameter list on the function
     $func->setParameterList($parameter_list);
     $func->setNumberOfRequiredParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isRequired() ? 1 : 0);
     }, 0));
     $func->setNumberOfOptionalParameters(array_reduce($parameter_list, function (int $carry, Parameter $parameter) : int {
         return $carry + ($parameter->isOptional() ? 1 : 0);
     }, 0));
     // Check to see if the comment specifies that the
     // function is deprecated
     $func->setIsDeprecated($comment->isDeprecated());
     $func->setSuppressIssueList($comment->getSuppressIssueList());
     // Take a look at function return types
     if ($node->children['returnType'] !== null) {
         // Get the type of the parameter
         $union_type = UnionType::fromNode($context, $code_base, $node->children['returnType']);
         $func->getUnionType()->addUnionType($union_type);
     }
     if ($comment->hasReturnUnionType()) {
         // See if we have a return type specified in the comment
         $union_type = $comment->getReturnType();
         assert(!$union_type->hasSelfType(), "Function referencing self in {$context}");
         $func->getUnionType()->addUnionType($union_type);
     }
     // Add params to local scope for user functions
     if (!$func->isInternal()) {
         $parameter_offset = 0;
         foreach ($func->getParameterList() as $i => $parameter) {
             if ($parameter->getUnionType()->isEmpty()) {
                 // If there is no type specified in PHP, check
                 // for a docComment with @param declarations. We
                 // assume order in the docComment matches the
                 // parameter order in the code
                 if ($comment->hasParameterWithNameOrOffset($parameter->getName(), $parameter_offset)) {
                     $comment_type = $comment->getParameterWithNameOrOffset($parameter->getName(), $parameter_offset)->getUnionType();
                     $parameter->getUnionType()->addUnionType($comment_type);
                 }
             }
             // If there's a default value on the parameter, check to
             // see if the type of the default is cool with the
             // specified type.
             if ($parameter->hasDefaultValue()) {
                 $default_type = $parameter->getDefaultValueType();
                 if (!$default_type->isEqualTo(NullType::instance()->asUnionType())) {
                     if (!$default_type->isEqualTo(NullType::instance()->asUnionType()) && !$default_type->canCastToUnionType($parameter->getUnionType())) {
                         Issue::maybeEmit($code_base, $context, Issue::TypeMismatchDefault, $node->lineno ?? 0, (string) $parameter->getUnionType(), $parameter->getName(), (string) $default_type);
                     }
                     $parameter->getUnionType()->addUnionType($default_type);
                 }
                 // If we have no other type info about a parameter,
                 // just because it has a default value of null
                 // doesn't mean that is its type. Any type can default
                 // to null
                 if ((string) $default_type === 'null' && !$parameter->getUnionType()->isEmpty()) {
                     $parameter->getUnionType()->addType(NullType::instance());
                 }
             }
             ++$parameter_offset;
         }
     }
     return $func;
 }
예제 #21
0
 /**
  * @param Node $node
  * A node to check types on
  *
  * @return UnionType
  * The resulting type(s) of the binary operation
  */
 public function visitBinaryAdd(Node $node) : UnionType
 {
     $left = UnionType::fromNode($this->context, $this->code_base, $node->children['left']);
     $right = UnionType::fromNode($this->context, $this->code_base, $node->children['right']);
     // fast-track common cases
     if ($left->isType(IntType::instance()) && $right->isType(IntType::instance())) {
         return IntType::instance()->asUnionType();
     }
     if (($left->isType(IntType::instance()) || $left->isType(FloatType::instance())) && ($right->isType(IntType::instance()) || $right->isType(FloatType::instance()))) {
         return FloatType::instance()->asUnionType();
     }
     $left_is_array = !empty($left->genericArrayElementTypes()) && empty($left->nonGenericArrayTypes());
     $right_is_array = !empty($right->genericArrayElementTypes()) && empty($right->nonGenericArrayTypes());
     if ($left_is_array && !$right->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $this->context->getFile(), $node->lineno);
         return new UnionType();
     } else {
         if ($right_is_array && !$left->canCastToUnionType(ArrayType::instance()->asUnionType())) {
             Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $this->context->getFile(), $node->lineno);
             return new UnionType();
         } else {
             if ($left_is_array || $right_is_array) {
                 // If it is a '+' and we know one side is an array
                 // and the other is unknown, assume array
                 return ArrayType::instance()->asUnionType();
             }
         }
     }
     return new UnionType([IntType::instance(), FloatType::instance()]);
 }
예제 #22
0
 /**
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return string
  * The class name represented by the given call
  */
 public function visitMethodCall(Node $node) : string
 {
     // Get the type returned by the first method
     // call.
     $union_type = UnionType::fromNode($this->context, $this->code_base, $node);
     // Find the subset of types that are viable
     // classes
     $viable_class_types = $union_type->nonNativeTypes()->nonGenericArrayTypes();
     // If there are no non-native types, give up
     if ($viable_class_types->isEmpty()) {
         return '';
     }
     // Return the first non-native type in the
     // list and hope its a class
     return (string) $viable_class_types->head();
 }
예제 #23
0
 /**
  * @param Node $node
  * A node to check types on
  *
  * @return UnionType
  * The resulting type(s) of the binary operation
  */
 public function visitBinaryAdd(Node $node) : UnionType
 {
     $left = UnionType::fromNode($this->context, $this->code_base, $node->children['left']);
     $right = UnionType::fromNode($this->context, $this->code_base, $node->children['right']);
     // fast-track common cases
     if ($left->isType(IntType::instance()) && $right->isType(IntType::instance())) {
         return IntType::instance()->asUnionType();
     }
     // If both left and right are arrays, then this is array
     // concatenation.
     if ($left->isGenericArray() && $right->isGenericArray()) {
         if ($left->isEqualTo($right)) {
             return $left;
         }
         return ArrayType::instance()->asUnionType();
     }
     if (($left->isType(IntType::instance()) || $left->isType(FloatType::instance())) && ($right->isType(IntType::instance()) || $right->isType(FloatType::instance()))) {
         return FloatType::instance()->asUnionType();
     }
     $left_is_array = !empty($left->genericArrayElementTypes()) && empty($left->nonGenericArrayTypes()) || $left->isType(ArrayType::instance());
     $right_is_array = !empty($right->genericArrayElementTypes()) && empty($right->nonGenericArrayTypes()) || $right->isType(ArrayType::instance());
     if ($left_is_array && !$right->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidRightOperand, $node->lineno ?? 0);
         return new UnionType();
     } elseif ($right_is_array && !$left->canCastToUnionType(ArrayType::instance()->asUnionType())) {
         Issue::maybeEmit($this->code_base, $this->context, Issue::TypeInvalidLeftOperand, $node->lineno ?? 0);
         return new UnionType();
     } elseif ($left_is_array || $right_is_array) {
         // If it is a '+' and we know one side is an array
         // and the other is unknown, assume array
         return ArrayType::instance()->asUnionType();
     }
     return new UnionType([IntType::instance(), FloatType::instance()]);
 }
예제 #24
0
 /**
  * Visit a node with kind `\ast\AST_PROP_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitPropDecl(Node $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     // Get a comment on the property declaration
     $comment = Comment::fromStringInContext($node->children[0]->docComment ?? '', $this->context);
     foreach ($node->children ?? [] as $i => $child_node) {
         // Ignore children which are not property elements
         if (!$child_node || $child_node->kind != \ast\AST_PROP_ELEM) {
             continue;
         }
         // Get the type of the default
         $union_type = UnionType::fromNode($this->context, $this->code_base, $child_node->children['default']);
         $property_name = $child_node->children['name'];
         assert(is_string($property_name), 'Property name must be a string. ' . 'Got ' . print_r($property_name, true) . ' at ' . $this->context);
         $property = new Property($this->context->withLineNumberStart($child_node->lineno ?? 0)->withLineNumberEnd($child_node->endLineno ?? -1), is_string($child_node->children['name']) ? $child_node->children['name'] : '_error_', $union_type, $node->flags ?? 0);
         // Add the property to the class
         $clazz->addProperty($this->code_base, $property);
         // Look for any @var declarations
         if ($variable = $comment->getVariableList()[$i] ?? null) {
             if ((string) $union_type != 'null' && !$union_type->canCastToUnionType($variable->getUnionType())) {
                 Log::err(Log::ETYPE, "assigning {$union_type} to property but {$property->getFQSEN()} is {$variable->getUnionType()}", $this->context->getFile(), $child_node->lineno);
             }
             // Set the declared type to the doc-comment type and add
             // |null if the default value is null
             $property->getUnionType()->addUnionType($variable->getUnionType());
         }
     }
     return $this->context;
 }