public function visitDim(Node $node) : UnionType
{
$union_type = self::unionTypeFromNode($this->code_base, $this->context, $node->children['expr'], $this->should_catch_issue_exception);
if ($union_type->isEmpty()) {
return $union_type;
}
// Figure out what the types of accessed array
// elements would be
$generic_types = $union_type->genericArrayElementTypes();
// If we have generics, we're all set
if (!$generic_types->isEmpty()) {
return $generic_types;
}
// If the only type is null, we don't know what
// accessed items will be
if ($union_type->isType(NullType::instance())) {
return new UnionType();
}
$element_types = new UnionType();
// You can access string characters via array index,
// so we'll add the string type to the result if we're
// indexing something that could be a string
if ($union_type->isType(StringType::instance()) || $union_type->canCastToUnionType(StringType::instance()->asUnionType())) {
$element_types->addType(StringType::instance());
}
// array offsets work on strings, unfortunately
// Double check that any classes in the type don't
// have ArrayAccess
$array_access_type = Type::fromNamespaceAndName('\\', 'ArrayAccess');
// Hunt for any types that are viable class names and
// see if they inherit from ArrayAccess
try {
foreach ($union_type->asClassList($this->code_base, $this->context) as $class) {
if ($class->getUnionType()->asExpandedTypes($this->code_base)->hasType($array_access_type)) {
return $element_types;
}
}
} catch (CodeBaseException $exception) {
}
if ($element_types->isEmpty()) {
$this->emitIssue(Issue::TypeArraySuspicious, $node->lineno ?? 0, (string) $union_type);
}
return $element_types;
}