Phan\Language\Element\FunctionInterface::getParameterForCaller PHP Method

getParameterForCaller() public method

In the case of variadic arguments, an infinite number of parameters exist. (The callee would see variadic arguments(T ...$args) as a single variable of type T[], while the caller sees a place expecting an expression of type T.
public getParameterForCaller ( integer $i ) : Parameter | null
$i integer - offset of the parameter.
return Parameter | null The parameter type that the **caller** observes.
    public function getParameterForCaller(int $i);

Usage Example

Example #1
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);
 }
All Usage Examples Of Phan\Language\Element\FunctionInterface::getParameterForCaller