Наследование: extends AddressableElement, implements Phan\Language\Element\FunctionInterface, use trait Phan\Analysis\Analyzable, use trait Phan\Memoize, use trait FunctionTrait, use trait Phan\Language\Element\ClosedScopeElement
Пример #1
0
 /**
  * @return Func[]
  * One or more (alternate) methods begotten from
  * reflection info and internal method data
  */
 public static function functionListFromSignature(CodeBase $code_base, FullyQualifiedFunctionName $fqsen, array $signature) : array
 {
     $context = new Context();
     $return_type = UnionType::fromStringInContext(array_shift($signature), $context);
     $func = new Func($context, $fqsen->getName(), $return_type, 0);
     $func->setFQSEN($fqsen);
     return self::functionListFromFunction($func, $code_base);
 }
Пример #2
0
 /**
  * @return Func[]
  * One or more (alternate) methods begotten from
  * reflection info and internal method data
  */
 public static function functionListFromReflectionFunction(CodeBase $code_base, \ReflectionFunction $reflection_function) : array
 {
     $context = new Context();
     $parts = explode('\\', $reflection_function->getName());
     $method_name = array_pop($parts);
     $namespace = '\\' . implode('\\', $parts);
     $fqsen = FullyQualifiedFunctionName::make($namespace, $method_name);
     $function = new Func($context, $fqsen->getName(), new UnionType(), 0, $fqsen);
     $function->setNumberOfRequiredParameters($reflection_function->getNumberOfRequiredParameters());
     $function->setNumberOfOptionalParameters($reflection_function->getNumberOfParameters() - $reflection_function->getNumberOfRequiredParameters());
     return self::functionListFromFunction($function, $code_base);
 }
Пример #3
0
 /**
  * Visit a node with kind `\ast\AST_FUNC_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitFuncDecl(Decl $node) : Context
 {
     $function_name = (string) $node->name;
     // Hunt for an un-taken alternate ID
     $alternate_id = 0;
     $function_fqsen = null;
     do {
         $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context)->withNamespace($this->context->getNamespace())->withAlternateId($alternate_id++);
     } while ($this->code_base->hasFunctionWithFQSEN($function_fqsen));
     $func = Func::fromNode($this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? 0), $this->code_base, $node);
     $func->setFQSEN($function_fqsen);
     $this->code_base->addFunction($func);
     // Send the context into the function and reset the scope
     $context = $this->context->withMethodFQSEN($function_fqsen)->withScope(new Scope());
     // Add each method parameter to the scope. We clone it
     // so that changes to the variable don't alter the
     // parameter definition
     foreach ($func->getParameterList() as $parameter) {
         $context->addScopeVariable(clone $parameter);
     }
     return $context;
 }
Пример #4
0
 /**
  * Visit a node with kind `\ast\AST_FUNC_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitFuncDecl(Decl $node) : Context
 {
     $function_name = (string) $node->name;
     // Hunt for an un-taken alternate ID
     $alternate_id = 0;
     $function_fqsen = null;
     do {
         $function_fqsen = FullyQualifiedFunctionName::fromStringInContext($function_name, $this->context)->withNamespace($this->context->getNamespace())->withAlternateId($alternate_id++);
     } while ($this->code_base->hasFunctionWithFQSEN($function_fqsen));
     $func = Func::fromNode($this->context->withLineNumberStart($node->lineno ?? 0)->withLineNumberEnd($node->endLineno ?? 0), $this->code_base, $node, $function_fqsen);
     $this->code_base->addFunction($func);
     // Send the context into the function and reset the scope
     $context = $this->context->withScope($func->getInternalScope());
     return $context;
 }
Пример #5
0
 /**
  * @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;
 }
Пример #6
0
 /**
  * @param Func $function
  * A function to add to the code base
  *
  * @return void
  */
 public function addFunction(Func $function)
 {
     // Add it to the map of functions
     $this->fqsen_func_map[$function->getFQSEN()] = $function;
     // Add it to the set of functions and methods
     $this->func_and_method_set->attach($function);
 }
Пример #7
0
 /**
  * Visit a node with kind `\ast\AST_CLOSURE`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitClosure(Decl $node) : Context
 {
     $closure_fqsen = FullyQualifiedFunctionName::fromClosureInContext($this->context->withLineNumberStart($node->lineno ?? 0));
     $func = Func::fromNode($this->context, $this->code_base, $node, $closure_fqsen);
     // If we have a 'this' variable in our current scope,
     // pass it down into the closure
     if ($this->context->getScope()->hasVariableWithName('this')) {
         $func->getInternalScope()->addVariable($this->context->getScope()->getVariableByName('this'));
     }
     // Make the closure reachable by FQSEN from anywhere
     $this->code_base->addFunction($func);
     if (!empty($node->children['uses']) && $node->children['uses']->kind == \ast\AST_CLOSURE_USES) {
         $uses = $node->children['uses'];
         foreach ($uses->children as $use) {
             if ($use->kind != \ast\AST_CLOSURE_VAR) {
                 $this->emitIssue(Issue::VariableUseClause, $node->lineno ?? 0);
                 continue;
             }
             $variable_name = (new ContextNode($this->code_base, $this->context, $use->children['name']))->getVariableName();
             if (empty($variable_name)) {
                 continue;
             }
             $variable = null;
             // Check to see if the variable exists in this scope
             if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
                 // If this is not pass-by-reference variable we
                 // have a problem
                 if (!($use->flags & \ast\flags\PARAM_REF)) {
                     $this->emitIssue(Issue::UndeclaredVariable, $node->lineno ?? 0, $variable_name);
                     continue;
                 } else {
                     // If the variable doesn't exist, but its
                     // a pass-by-reference variable, we can
                     // just create it
                     $variable = Variable::fromNodeInContext($use, $this->context, $this->code_base, false);
                 }
             } else {
                 $variable = $this->context->getScope()->getVariableByName($variable_name);
                 // If this isn't a pass-by-reference variable, we
                 // clone the variable so state within this scope
                 // doesn't update the outer scope
                 if (!($use->flags & \ast\flags\PARAM_REF)) {
                     $variable = clone $variable;
                 }
             }
             // Pass the variable into a new scope
             $func->getInternalScope()->addVariable($variable);
         }
     }
     // Add all parameters to the scope
     if (!empty($node->children['params']) && $node->children['params']->kind == \ast\AST_PARAM_LIST) {
         $params = $node->children['params'];
         foreach ($params->children as $param) {
             // Read the parameter
             $parameter = Parameter::fromNode($this->context, $this->code_base, $param);
             // Add it to the scope
             $func->getInternalScope()->addVariable($parameter);
         }
     }
     if ($this->analyzeFunctionLikeIsGenerator($node)) {
         $this->setReturnTypeOfGenerator($func, $node);
     }
     return $this->context->withScope($func->getInternalScope());
 }
Пример #8
0
 /**
  * @param CodeBase $code_base
  * The code base in which the function exists
  *
  * @param Func $function
  * A function being analyzed
  *
  * @return void
  */
 public function analyzeFunction(CodeBase $code_base, Func $function)
 {
     // As an example, we test to see if the name of the
     // function is `function`, and emit an issue if it is.
     if ($function->getName() == 'function') {
         $this->emitIssue($code_base, $function->getContext(), 'DemoPluginFunctionName', "Function {$function->getFQSEN()} cannot be called `function`");
     }
 }