Phan\Analysis\PostOrderAnalysisVisitor::analyzeCallToMethod PHP Method

analyzeCallToMethod() private method

Analyze the parameters and arguments for a call to the given method or function
private analyzeCallToMethod ( CodeBase $code_base, Phan\Language\Element\FunctionInterface $method, ast\Node $node ) : null
$code_base Phan\CodeBase
$method Phan\Language\Element\FunctionInterface
$node ast\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 fucking 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);
    }