public function visitCall(Node $node) : Context
{
// Only look at things of the form
// `is_string($variable)`
if (count($node->children['args']->children) !== 1 || !$node->children['args']->children[0] instanceof Node || $node->children['args']->children[0]->kind !== \ast\AST_VAR || !$node->children['expr'] instanceof Node || empty($node->children['expr']->children['name'] ?? null) || !is_string($node->children['expr']->children['name'])) {
return $this->context;
}
// Translate the function name into the UnionType it asserts
$map = array('is_array' => 'array', 'is_bool' => 'bool', 'is_callable' => 'callable', 'is_double' => 'float', 'is_float' => 'float', 'is_int' => 'int', 'is_integer' => 'int', 'is_long' => 'int', 'is_null' => 'null', 'is_numeric' => 'string|int|float', 'is_object' => 'object', 'is_real' => 'float', 'is_resource' => 'resource', 'is_scalar' => 'int|float|bool|string|null', 'is_string' => 'string', 'empty' => 'null');
$function_name = $node->children['expr']->children['name'];
if (!isset($map[$function_name])) {
return $this->context;
}
$type = UnionType::fromFullyQualifiedString($map[$function_name]);
$context = $this->context;
try {
// Get the variable we're operating on
$variable = (new ContextNode($this->code_base, $this->context, $node->children['args']->children[0]))->getVariable();
if ($variable->getUnionType()->isEmpty()) {
$variable->getUnionType()->addType(NullType::instance());
}
// Make a copy of the variable
$variable = clone $variable;
$variable->setUnionType(clone $variable->getUnionType());
// Change the type to match the is_a relationship
if ($type->isType(ArrayType::instance()) && $variable->getUnionType()->hasGenericArray()) {
// If the variable is already a generic array,
// note that it can be an arbitrary array without
// erasing the existing generic type.
$variable->getUnionType()->addUnionType($type);
} else {
// Otherwise, overwrite the type for any simple
// primitive types.
$variable->setUnionType($type);
}
// Overwrite the variable with its new type in this
// scope without overwriting other scopes
$context = $context->withScopeVariable($variable);
} catch (\Exception $exception) {
// Swallow it
}
return $context;
}