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 ) : |
||
$i | integer | - offset of the parameter. |
Результат | The parameter type that the **caller** observes. |
/** * 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); }
/** * @param CodeBase $code_base * The global code base * * @param FunctionInterface $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 * * @return null * * @see \Phan\Deprecated\Pass2::arglist_type_check * Formerly `function arglist_type_check` */ private static function analyzeParameterList(CodeBase $code_base, FunctionInterface $method, Node $node, Context $context) { // There's nothing reasonable we can do here if ($method instanceof Method) { if ($method->getIsMagicCall() || $method->getIsMagicCallStatic()) { return; } } foreach ($node->children ?? [] as $i => $argument) { // Get the parameter associated with this argument $parameter = $method->getParameterForCaller($i); // This issue should be caught elsewhere if (!$parameter) { continue; } // If this is a pass-by-reference parameter, make sure // we're passing an allowable argument if ($parameter->isPassByReference()) { if (!$argument instanceof \ast\Node || $argument->kind != \ast\AST_VAR && $argument->kind != \ast\AST_DIM && $argument->kind != \ast\AST_PROP && $argument->kind != \ast\AST_STATIC_PROP) { Issue::maybeEmit($code_base, $context, Issue::TypeNonVarPassByRef, $node->lineno ?? 0, $i + 1, (string) $method->getFQSEN()); } else { $variable_name = (new ContextNode($code_base, $context, $argument))->getVariableName(); if (Type::isSelfTypeString($variable_name) && !$context->isInClassScope() && $argument->kind == \ast\AST_STATIC_PROP && $argument->kind == \ast\AST_PROP) { Issue::maybeEmit($code_base, $context, Issue::ContextNotObject, $node->lineno ?? 0, "{$variable_name}"); } } } // Get the type of the argument. We'll check it against // the parameter in a moment $argument_type = UnionType::fromNode($context, $code_base, $argument); // Expand it to include all parent types up the chain $argument_type_expanded = $argument_type->asExpandedTypes($code_base); // Check the method to see if it has the correct // parameter types. If not, keep hunting through // alternates of the method until we find one that // takes the correct types $alternate_parameter = null; $alternate_found = false; foreach ($method->alternateGenerator($code_base) as $alternate_id => $alternate_method) { // Get the parameter associated with this argument $candidate_alternate_parameter = $alternate_method->getParameterForCaller($i); if (is_null($candidate_alternate_parameter)) { continue; } $alternate_parameter = $candidate_alternate_parameter; // See if the argument can be cast to the // parameter if ($argument_type_expanded->canCastToUnionType($alternate_parameter->getUnionType())) { $alternate_found = true; break; } } if (!$alternate_found) { $parameter_name = $alternate_parameter ? $alternate_parameter->getName() : 'unknown'; $parameter_type = $alternate_parameter ? $alternate_parameter->getUnionType() : 'unknown'; if (is_object($parameter_type) && $parameter_type->hasTemplateType()) { // Don't worry about template types } elseif ($method->isInternal()) { // If we are not in strict mode and we accept a string parameter // and the argument we are passing has a __toString method then it is ok if (!$context->getIsStrictTypes() && $parameter_type->hasType(StringType::instance())) { try { foreach ($argument_type_expanded->asClassList($code_base, $context) as $clazz) { if ($clazz->hasMethodWithName($code_base, "__toString")) { return; } } } catch (CodeBaseException $e) { // Swallow "Cannot find class", go on to emit issue } } Issue::maybeEmit($code_base, $context, Issue::TypeMismatchArgumentInternal, $node->lineno ?? 0, $i + 1, $parameter_name, $argument_type_expanded, (string) $method->getFQSEN(), (string) $parameter_type); } else { Issue::maybeEmit($code_base, $context, Issue::TypeMismatchArgument, $node->lineno ?? 0, $i + 1, $parameter_name, $argument_type_expanded, (string) $method->getFQSEN(), (string) $parameter_type, $method->getFileRef()->getFile(), $method->getFileRef()->getLineNumberStart()); } } } }