unionTypeFromClassNode() public static method

public static unionTypeFromClassNode ( CodeBase $code_base, Context $context, ast\Node | mixed $node ) : UnionType
$code_base Phan\CodeBase The code base within which we're operating
$context Phan\Language\Context $context The context of the parser at the node for which we'd like to determine a type
$node ast\Node | mixed The node for which we'd like to determine its type
return Phan\Language\UnionType The UnionType associated with the given node in the given Context within the given CodeBase
Example #1
0
 /**
  * @param Node $node
  * A node of the type indicated by the method name that we'd
  * like to figure out the type that it produces.
  *
  * @return string
  * The class name represented by the given call
  */
 public function visitNew(Node $node) : string
 {
     // Things of the form `new $class_name();`
     if ($node->children['class']->kind == \ast\AST_VAR) {
         return '';
     }
     // Anonymous class
     // $v = new class { ... }
     if ($node->children['class']->kind == \ast\AST_CLASS && $node->children['class']->flags & \ast\flags\CLASS_ANONYMOUS) {
         return AST::unqualifiedNameForAnonymousClassNode($node->children['class'], $this->context);
     }
     // Things of the form `new $method->name()`
     if ($node->children['class']->kind !== \ast\AST_NAME) {
         return '';
     }
     $class_name = $node->children['class']->children['name'];
     if (!in_array($class_name, ['self', 'static', 'parent'])) {
         return (string) UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $node->children['class']);
     }
     if (!$this->context->isInClassScope()) {
         Log::err(Log::ESTATIC, "Cannot access {$class_name}:: when no class scope is active", $this->context->getFile(), $node->lineno);
         return '';
     }
     if ($class_name == 'static') {
         return (string) $this->context->getClassFQSEN();
     }
     if ($class_name == 'self') {
         if ($this->context->isGlobalScope()) {
             assert(false, "Unimplemented branch is required for {$this->context}");
         } else {
             return (string) $this->context->getClassFQSEN();
         }
     }
     if ($class_name == 'parent') {
         $clazz = $this->context->getClassInScope($this->code_base);
         if (!$clazz->hasParentClassFQSEN()) {
             return '';
         }
         return (string) $clazz->getParentClassFQSEN();
     }
     return '';
 }
Example #2
0
 /**
  * @param Node|string $method_name
  * Either then name of the method or a node that
  * produces the name of the method.
  *
  * @param bool $is_static
  * Set to true if this is a static method call
  *
  * @return Method
  * A method with the given name on the class referenced
  * from the given node
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws CodeBaseExtension
  * An exception is thrown if we can't find the given
  * method
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  *
  * @throws IssueException
  */
 public function getMethod($method_name, bool $is_static) : Method
 {
     if ($method_name instanceof Node) {
         // The method_name turned out to be a variable.
         // There isn't much we can do to figure out what
         // it's referring to.
         throw new NodeException($method_name, "Unexpected method node");
     }
     assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList();
     } catch (CodeBaseException $exception) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredClassMethod)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $exception->getFQSEN()]));
     }
     // If there were no classes on the left-type, figure
     // out what we were trying to call the method on
     // and send out an error.
     if (empty($class_list)) {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']);
         if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasAnyType([MixedType::instance(), ObjectType::instance(), StringType::instance()]) && !(Config::get()->null_casts_as_any_type && $union_type->hasType(NullType::instance()))) {
             throw new IssueException(Issue::fromType(Issue::NonClassMethodCall)($this->context->getFile(), $this->node->lineno ?? 0, [$method_name, (string) $union_type]));
         }
         throw new NodeException($this->node, "Can't figure out method call for {$method_name}");
     }
     // Hunt to see if any of them have the method we're
     // looking for
     foreach ($class_list as $i => $class) {
         if ($class->hasMethodWithName($this->code_base, $method_name)) {
             return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context);
         } else {
             if ($class->hasMethodWithName($this->code_base, '__call')) {
                 return $class->getMethodByNameInContext($this->code_base, '__call', $this->context);
             }
         }
     }
     // Figure out an FQSEN for the method we couldn't find
     $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name);
     if ($is_static) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredStaticMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen]));
     }
     throw new IssueException(Issue::fromType(Issue::UndeclaredMethod)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $method_fqsen]));
 }
Example #3
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitCatch(Node $node) : Context
 {
     try {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $node->children['class']);
         $class_list = (new ContextNode($this->code_base, $this->context, $node->children['class']))->getClassList();
     } catch (CodeBaseException $exception) {
         Issue::emit(Issue::UndeclaredClassCatch, $this->context->getFile(), $node->lineno ?? 0, (string) $exception->getFQSEN());
     }
     $variable_name = (new ContextNode($this->code_base, $this->context, $node->children['var']))->getVariableName();
     if (!empty($variable_name)) {
         $variable = Variable::fromNodeInContext($node->children['var'], $this->context, $this->code_base, false);
         if (!$union_type->isEmpty()) {
             $variable->setUnionType($union_type);
         }
         $this->context->addScopeVariable($variable);
     }
     return $this->context;
 }
Example #4
0
 /**
  * @return Clazz[]
  * A list of classes representing the non-native types
  * associated with the given node
  *
  * @throws CodeBaseException
  * An exception is thrown if a non-native type does not have
  * an associated class
  */
 public function getClassList()
 {
     $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node);
     $class_list = [];
     foreach ($union_type->asClassList($this->code_base) as $i => $clazz) {
         $class_list[] = $clazz;
     }
     return $class_list;
 }
Example #5
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitCatch(Node $node) : Context
 {
     try {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $node->children['class']);
         $class_list = AST::classListFromNodeInContext($this->code_base, $this->context, $node->children['class']);
     } catch (CodeBaseException $exception) {
         Log::err(Log::EUNDEF, "catching undeclared class {$exception->getFQSEN()}", $this->context->getFile(), $node->lineno);
     }
     $variable_name = AST::variableName($node->children['var']);
     if (!empty($variable_name)) {
         $variable = Variable::fromNodeInContext($node->children['var'], $this->context, $this->code_base, false);
         if (!$union_type->isEmpty()) {
             $variable->setUnionType($union_type);
         }
         $this->context->addScopeVariable($variable);
     }
     return $this->context;
 }
Example #6
0
 /**
  * @param Node|string $method_name
  * Either then name of the method or a node that
  * produces the name of the method.
  *
  * @param bool $is_static
  * Set to true if this is a static method call
  *
  * @return Method
  * A method with the given name on the class referenced
  * from the given node
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws CodeBaseExtension
  * An exception is thrown if we can't find the given
  * method
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public function getMethod($method_name, bool $is_static) : Method
 {
     if ($method_name instanceof Node) {
         // The method_name turned out to be a variable.
         // There isn't much we can do to figure out what
         // it's referring to.
         throw new NodeException($method_name, "Unexpected method node");
     }
     assert(is_string($method_name), "Method name must be a string. Found non-string at {$this->context}");
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList();
     } catch (CodeBaseException $exception) {
         // We can give a more explicit message
         throw new CodeBaseException($exception->getFQSEN(), "Can't access method {$method_name} from undeclared class {$exception->getFQSEN()}");
     }
     // If there were no classes on the left-type, figure
     // out what we were trying to call the method on
     // and send out an error.
     if (empty($class_list)) {
         $union_type = UnionTypeVisitor::unionTypeFromClassNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']);
         if (!$union_type->isEmpty() && $union_type->isNativeType() && !$union_type->hasType(MixedType::instance())) {
             throw new TypeException("Calling method on non-class type {$union_type}");
         }
         throw new NodeException($this->node, "Can't figure out method call for {$method_name}");
     }
     // Hunt to see if any of them have the method we're
     // looking for
     foreach ($class_list as $i => $class) {
         if ($class->hasMethodWithName($this->code_base, $method_name)) {
             return $class->getMethodByNameInContext($this->code_base, $method_name, $this->context);
         }
     }
     // Figure out an FQSEN for the method we couldn't find
     $method_fqsen = FullyQualifiedMethodName::make($class_list[0]->getFQSEN(), $method_name);
     if ($is_static) {
         throw new CodeBaseException($method_fqsen, "static call to undeclared method {$method_fqsen}");
     }
     throw new CodeBaseException($method_fqsen, "call to undeclared method {$method_fqsen}");
 }
Example #7
0
File: AST.php Project: mgonyan/phan
 /**
  * @param CodeBase $code_base
  * The code base in which we're looking for classes
  *
  * @param Context $context
  * The context in which the node exists
  *
  * @param Node $node
  * The node we want to get a type for and classes from
  * the types
  *
  * @return Clazz[]
  * A list of classes representing the non-native types
  * associated with the given node
  *
  * @throws CodeBaseException
  * An exception is thrown if a non-native type does not have
  * an associated class
  */
 public static function classListFromNodeInContext(CodeBase $code_base, Context $context, $node)
 {
     $union_type = UnionTypeVisitor::unionTypeFromClassNode($code_base, $context, $node);
     $class_list = [];
     foreach ($union_type->asClassList($code_base) as $i => $clazz) {
         $class_list[] = $clazz;
     }
     return $class_list;
 }