/**
* @param CodeBase $code_base
* The global code base
*
* @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
*
* @return null
*
* @see \Phan\Deprecated\Pass2::arglist_type_check
* Formerly `function arglist_type_check`
*/
private static function analyzeParameterList(CodeBase $code_base, Method $method, Node $node, Context $context)
{
foreach ($node->children ?? [] as $i => $argument) {
// Get the parameter associated with this argument
$parameter = $method->getParameterList()[$i] ?? null;
// 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) {
Log::err(Log::ETYPE, "Only variables can be passed by reference at arg#" . ($i + 1) . " of {$method->getFQSEN()}()", $context->getFile(), $node->lineno);
} else {
$variable_name = AST::variableName($argument);
if ($argument->kind == \ast\AST_STATIC_PROP) {
if (in_array($variable_name, ['self', 'static', 'parent'])) {
Log::err(Log::ESTATIC, "Using {$variable_name}:: when not in object context", $context->getFile(), $argument->lineno);
}
}
}
}
// 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);
/* TODO see issue #42
If argument is an object and it has a String union type,
then we need to ignore that in strict_types=1 mode.
if ($argument instanceof \ast\Node) {
if(!empty($argument->children['class'])) {
// arg is an object
if ($method->getContext()->getStrictTypes()) {
...
}
}
}
or maybe UnionType::fromNode should check strict_types and
not return the string union type
or we shouldn't add the string type at all when a class
has a __toString() and instead set a flag and check that
instead
*/
// 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) {
if (empty($alternate_method->getParameterList()[$i])) {
continue;
}
// Get the parameter associated with this argument
$alternate_parameter = $alternate_method->getParameterList()[$i] ?? null;
// Expand the types to find all parents and traits
$alternate_parameter_type_expanded = $alternate_parameter->getUnionType()->asExpandedTypes($code_base);
// See if the argument can be cast to the
// parameter
if ($argument_type_expanded->canCastToUnionType($alternate_parameter_type_expanded)) {
$alternate_found = true;
break;
}
}
if (!$alternate_found) {
$parameter_name = $alternate_parameter ? $alternate_parameter->getName() : 'unknown';
$parameter_type = $alternate_parameter ? $alternate_parameter->getUnionType() : 'unknown';
if ($method->getContext()->isInternal()) {
Log::err(Log::ETYPE, "arg#" . ($i + 1) . "({$parameter_name}) is " . "{$argument_type_expanded} but {$method->getFQSEN()}() " . "takes {$parameter_type}", $context->getFile(), $node->lineno);
} else {
Log::err(Log::ETYPE, "arg#" . ($i + 1) . "({$parameter_name}) is " . "{$argument_type_expanded} but {$method->getFQSEN()}() " . "takes {$parameter_type} " . "defined at {$method->getContext()->getFile()}:{$method->getContext()->getLineNumberStart()}", $context->getFile(), $node->lineno);
}
}
}
}