/**
* @param Node $node
* A node to parse
*
* @return Context
* A new or an unchanged context resulting from
* parsing the node
*/
public function visitProp(Node $node) : Context
{
$property_name = $node->children['prop'];
// Things like $foo->$bar
if (!is_string($property_name)) {
return $this->context;
}
assert(is_string($property_name), "Property must be string in context {$this->context}");
try {
$class_list = (new ContextNode($this->code_base, $this->context, $node->children['expr']))->getClassList();
} catch (CodeBaseException $exception) {
// This really shouldn't happen since the code
// parsed cleanly. This should fatal.
// throw $exception;
return $this->context;
} catch (\Exception $exception) {
// If we can't figure out what kind of a class
// this is, don't worry about it
return $this->context;
}
foreach ($class_list as $clazz) {
// Check to see if this class has the property or
// a setter
if (!$clazz->hasPropertyWithName($this->code_base, $property_name)) {
if (!$clazz->hasMethodWithName($this->code_base, '__set')) {
continue;
}
}
try {
$property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
} catch (IssueException $exception) {
$exception->getIssueInstance()();
return $this->context;
}
if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
Issue::emit(Issue::TypeMismatchProperty, $this->context->getFile(), $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
return $this->context;
}
// After having checked it, add this type to it
$property->getUnionType()->addUnionType($this->right_type);
return $this->context;
}
if (Config::get()->allow_missing_properties) {
try {
// Create the property
(new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
} catch (\Exception $exception) {
// swallow it
}
} else {
if (!empty($class_list)) {
Issue::emit(Issue::UndeclaredProperty, $this->context->getFile(), $node->lineno ?? 0, $property_name);
} else {
// If we hit this part, we couldn't figure out
// the class, so we ignore the issue
}
}
return $this->context;
}