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());
}
}
}
}