getClassFQSEN() public method

public getClassFQSEN ( ) : FullyQualifiedClassName
return Phan\Language\FQSEN\FullyQualifiedClassName A fully-qualified structural element name describing the current class in scope.
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 visitVar(Node $node) : string
 {
     // $$var->method()
     if ($node->children['name'] instanceof Node) {
         return '';
     }
     // $this->method()
     if ($node->children['name'] == 'this') {
         if (!$this->context->isInClassScope()) {
             Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
             return '';
         }
         return (string) $this->context->getClassFQSEN();
     }
     $variable_name = $node->children['name'];
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         // Got lost, couldn't find the variable in the current scope
         // If it really isn't defined, it will be caught by the
         // undefined var error
         return '';
     }
     $variable = $this->context->getScope()->getVariableWithName($variable_name);
     // Hack - loop through the possible types of the var and assume
     // first found class is correct
     foreach ($variable->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
         $child_class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
         if ($this->code_base->hasClassWithFQSEN($child_class_fqsen)) {
             return (string) FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
         }
     }
     // We land here if we have a variable
     // with a native type or no known type.
     return '';
 }
Example #2
0
 /**
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitNew(Node $node) : Context
 {
     try {
         $context_node = new ContextNode($this->code_base, $this->context, $node);
         $method = $context_node->getMethod('__construct', false);
         // Add a reference to each class this method
         // could be called on
         foreach ($context_node->getClassList() as $class) {
             $class->addReference($this->context);
         }
         $this->analyzeCallToMethod($this->code_base, $method, $node);
         $class_list = $context_node->getClassList();
         foreach ($class_list as $class) {
             // Make sure we're not instantiating an abstract
             // class
             if ($class->isAbstract() && (!$this->context->hasClassFQSEN() || $class->getFQSEN() != $this->context->getClassFQSEN())) {
                 Issue::emit(Issue::TypeInstantiateAbstract, $this->context->getFile(), $node->lineno ?? 0, (string) $class->getFQSEN());
             }
             // Make sure we're not instantiating an interface
             if ($class->isInterface()) {
                 Issue::emit(Issue::TypeInstantiateInterface, $this->context->getFile(), $node->lineno ?? 0, (string) $class->getFQSEN());
             }
         }
     } catch (IssueException $exception) {
         $exception->getIssueInstance()();
     } catch (\Exception $exception) {
         // If we can't figure out what kind of a call
         // this is, don't worry about it
         return $this->context;
     }
     return $this->context;
 }
Example #3
0
 public function visitNew(Node $node) : bool
 {
     if (!$this->classExists()) {
         return $this->classExistsOrIsNative($node);
     }
     $clazz = $this->code_base->getClassByFQSEN($this->class_fqsen);
     if ($clazz->isAbstract()) {
         if (!$this->context->hasClassFQSEN() || $clazz->getFQSEN() != $this->context->getClassFQSEN()) {
             Log::err(Log::ETYPE, "Cannot instantiate abstract class {$this->class_name}", $this->context->getFile(), $node->lineno);
             return false;
         }
         return true;
     }
     if ($clazz->isInterface()) {
         if (!UnionType::fromStringInContext($this->class_name, $this->context)->isNativeType()) {
             Log::err(Log::ETYPE, "Cannot instantiate interface {$this->class_name}", $this->context->getFile(), $node->lineno);
             return false;
         }
     }
     return true;
 }
Example #4
0
 private function visitClassNode(Node $node) : UnionType
 {
     // Things of the form `new $class_name();`
     if ($node->kind == \ast\AST_VAR) {
         return new UnionType();
     }
     // Anonymous class of form `new class { ... }`
     if ($node->kind == \ast\AST_CLASS && $node->flags & \ast\flags\CLASS_ANONYMOUS) {
         // Generate a stable name for the anonymous class
         $anonymous_class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
         // Turn that into a fully qualified name
         $fqsen = FullyQualifiedClassName::fromStringInContext($anonymous_class_name, $this->context);
         // Turn that into a union type
         return Type::fromFullyQualifiedString((string) $fqsen)->asUnionType();
     }
     // Things of the form `new $method->name()`
     if ($node->kind !== \ast\AST_NAME) {
         return new UnionType();
     }
     // Get the name of the class
     $class_name = $node->children['name'];
     // If this is a straight-forward class name, recurse into the
     // class node and get its type
     if (!Type::isSelfTypeString($class_name)) {
         // TODO: does anyone else call this method?
         return self::unionTypeFromClassNode($this->code_base, $this->context, $node);
     }
     // This is a self-referential node
     if (!$this->context->isInClassScope()) {
         Log::err(Log::ESTATIC, "Cannot access {$class_name} when not in a class scope", $this->context->getFile(), $node->lineno);
         return new UnionType();
     }
     // Reference to a parent class
     if ($class_name === 'parent') {
         $class = $this->context->getClassInScope($this->code_base);
         if (!$class->hasParentClassFQSEN()) {
             Log::err(Log::ESTATIC, "Reference to parent of parentless class {$class->getFQSEN()}", $this->context->getFile(), $node->lineno);
             return new UnionType();
         }
         return Type::fromFullyQualifiedString((string) $class->getParentClassFQSEN())->asUnionType();
     }
     return Type::fromFullyQualifiedString((string) $this->context->getClassFQSEN())->asUnionType();
 }
Example #5
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 visitVar(Node $node) : string
 {
     // $$var->method()
     if ($node->children['name'] instanceof Node) {
         return '';
     }
     // $this->method()
     if ($node->children['name'] == 'this') {
         if (!$this->context->isInClassScope()) {
             Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
             return '';
         }
         return (string) $this->context->getClassFQSEN();
     }
     $variable_name = $node->children['name'];
     if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
         // Got lost, couldn't find the variable in the current scope
         // If it really isn't defined, it will be caught by the
         // undefined var error
         return '';
     }
     $variable = $this->context->getScope()->getVariableWithName($variable_name);
     $union_type = $variable->getUnionType()->nonNativeTypes()->nonGenericArrayTypes();
     // If there are no candidate classes, we'll emit whatever
     // we have so that we can differentiate between
     // no-known-type and a shitty type
     if ($union_type->isEmpty()) {
         if (!$variable->getUnionType()->isEmpty() && !$variable->getUnionType()->hasType(MixedType::instance()) && !$variable->getUnionType()->hasType(ArrayType::instance()) && !$variable->getUnionType()->hasType(ObjectType::instance())) {
             $type = (string) $variable->getUnionType();
             throw new TypeException("Calling method on non-class type {$type}");
         }
         // No viable class types for the variable.
         return '';
     }
     $class_fqsen = $this->chooseSingleFQSEN(array_map(function (Type $type) {
         return $type->asFQSEN();
     }, $union_type->getTypeList()));
     if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
         return (string) $class_fqsen;
     }
     // We couldn't find any viable classes
     return '';
 }
Example #6
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 #7
0
 /**
  * @param string $string
  * A string representing a type
  *
  * @param Context $context
  * The context in which the type string was
  * found
  *
  * @return Type
  * Parse a type from the given string
  */
 public static function fromStringInContext(string $string, Context $context) : Type
 {
     assert($string !== '', "Type cannot be empty in {$context}");
     $namespace = null;
     // Extract the namespace if the type string is
     // fully-qualified
     if ('\\' === $string[0]) {
         list($namespace, $string) = self::namespaceAndTypeFromString($string);
     }
     $type_name = $string;
     // @var bool
     // True if this type name if of the form 'C[]'
     $is_generic_array_type = self::isGenericArrayString($type_name);
     // If this is a generic array type, get the name of
     // the type of each element
     $non_generic_array_type_name = $type_name;
     if ($is_generic_array_type && false !== ($pos = strpos($type_name, '[]'))) {
         $non_generic_array_type_name = substr($type_name, 0, $pos);
     }
     // Check to see if the type name is mapped via
     // a using clause.
     //
     // Gotta check this before checking for native types
     // because there are monsters out there that will
     // remap the names via things like `use \Foo\String`.
     if ($context->hasNamespaceMapFor(T_CLASS, $non_generic_array_type_name)) {
         $fqsen = $context->getNamespaceMapFor(T_CLASS, $non_generic_array_type_name);
         if ($is_generic_array_type) {
             return GenericArrayType::fromElementType(Type::make($fqsen->getNamespace(), $fqsen->getName()));
         }
         return Type::make($fqsen->getNamespace(), $fqsen->getName());
     }
     // If this was a fully qualified type, we're all
     // set
     if (!empty($namespace)) {
         return self::fromNamespaceAndName($namespace, $type_name);
     }
     if ($is_generic_array_type && self::isNativeTypeString($type_name)) {
         return self::fromInternalTypeName($type_name);
     } else {
         // Check to see if its a builtin type
         switch (self::canonicalNameFromName($type_name)) {
             case 'array':
                 return \Phan\Language\Type\ArrayType::instance();
             case 'bool':
                 return \Phan\Language\Type\BoolType::instance();
             case 'callable':
                 return \Phan\Language\Type\CallableType::instance();
             case 'float':
                 return \Phan\Language\Type\FloatType::instance();
             case 'int':
                 return \Phan\Language\Type\IntType::instance();
             case 'mixed':
                 return \Phan\Language\Type\MixedType::instance();
             case 'null':
                 return \Phan\Language\Type\NullType::instance();
             case 'object':
                 return \Phan\Language\Type\ObjectType::instance();
             case 'resource':
                 return \Phan\Language\Type\ResourceType::instance();
             case 'string':
                 return \Phan\Language\Type\StringType::instance();
             case 'void':
                 return \Phan\Language\Type\VoidType::instance();
         }
     }
     // Things like `self[]` or `$this[]`
     if ($is_generic_array_type && self::isSelfTypeString($non_generic_array_type_name) && $context->isInClassScope()) {
         // Callers of this method should be checking on their own
         // to see if this type is a reference to 'parent' and
         // dealing with it there. We don't want to have this
         // method be dependent on the code base
         assert('parent' !== $non_generic_array_type_name, __METHOD__ . " does not know how to handle the type name 'parent' in {$context}");
         return GenericArrayType::fromElementType(static::fromFullyQualifiedString((string) $context->getClassFQSEN()));
     }
     // If this is a type referencing the current class
     // in scope such as 'self' or 'static', return that.
     if (self::isSelfTypeString($type_name) && $context->isInClassScope()) {
         // Callers of this method should be checking on their own
         // to see if this type is a reference to 'parent' and
         // dealing with it there. We don't want to have this
         // method be dependent on the code base
         assert('parent' !== $type_name, __METHOD__ . " does not know how to handle the type name 'parent' in {$context}");
         return static::fromFullyQualifiedString((string) $context->getClassFQSEN());
     }
     // Attach the context's namespace to the type name
     return self::fromNamespaceAndName($context->getNamespace() ?: '\\', $type_name);
 }
Example #8
0
 /**
  * @param string $name
  * The name of the property
  *
  * @param Context $context
  * The context of the caller requesting the property
  *
  * @return Property
  * A property with the given name
  *
  * @throws AccessException
  * An exception may be thrown if the caller does not
  * have access to the given property from the given
  * context
  */
 public function getPropertyByNameInContext(CodeBase $code_base, string $name, Context $context) : Property
 {
     $property = $code_base->getProperty($this->getFQSEN(), $name);
     // If we're getting the property from outside of this
     // class and the property isn't public and we don't
     // have a getter or setter, emit an access error
     if ((!$context->hasClassFQSEN() || $context->getClassFQSEN() != $this->getFQSEN()) && !$property->isPublic() && !$this->hasMethodWithName($code_base, '__get') && !$this->hasMethodWithName($code_base, '__set')) {
         if ($property->isPrivate()) {
             throw new AccessException("Cannot access private property {$this->getFQSEN()}::\${$property->getName()}");
         }
         if ($property->isProtected()) {
             throw new AccessException("Cannot access protected property {$this->getFQSEN()}::\${$property->getName()}");
         }
     }
     return $property;
 }
 /**
  * @param Context $context
  * The context in which the FQSEN string was found
  *
  * @param $fqsen_string
  * An FQSEN string like '\Namespace\Class::methodName'
  *
  * @return FullyQualifiedMethodName
  */
 public static function fromStringInContext(string $fqsen_string, Context $context)
 {
     // Test to see if we have a class defined
     if (false === strpos($fqsen_string, '::')) {
         assert($context->isInClassScope(), "Cannot reference class element without class name when not in class scope.");
         $fully_qualified_class_name = $context->getClassFQSEN();
     } else {
         assert(false !== strpos($fqsen_string, '::'), "Fully qualified class element lacks '::' delimiter");
         list($class_name_string, $fqsen_string) = explode('::', $fqsen_string);
         $fully_qualified_class_name = FullyQualifiedClassName::fromStringInContext($class_name_string, $context);
     }
     // Split off the alternate ID
     $parts = explode(',', $fqsen_string);
     $name = $parts[0];
     $alternate_id = (int) ($parts[1] ?? 0);
     assert(is_int($alternate_id), "Alternate must be an integer");
     return static::make($fully_qualified_class_name, $name, $alternate_id);
 }
Example #10
0
 /**
  * @param string $string
  * A string representing a type
  *
  * @param Context $context
  * The context in which the type string was
  * found
  *
  * @return Type
  * Parse a type from the given string
  */
 public static function fromStringInContext(string $string, Context $context) : Type
 {
     assert($string !== '', "Type cannot be empty in {$context}");
     $namespace = null;
     // Extract the namespace if the type string is
     // fully-qualified
     if ('\\' === $string[0]) {
         list($namespace, $string) = self::namespaceAndTypeFromString($string);
     }
     $type_name = strtolower($string);
     // Check to see if the type name is mapped via
     // a using clause.
     //
     // Gotta check this before checking for native types
     // because there are monsters out there that will
     // remap the names via things like `use \Foo\String`.
     if ($context->hasNamespaceMapFor(T_CLASS, $type_name)) {
         $fqsen = $context->getNamespaceMapFor(T_CLASS, $type_name);
         return new Type($fqsen->getNamespace(), $fqsen->getName());
     }
     // If this was a fully qualified type, we're all
     // set
     if (!empty($namespace)) {
         return self::fromNamespaceAndName($namespace, $type_name);
     }
     // Check to see if its a builtin type
     switch (self::canonicalNameFromName($type_name)) {
         case 'array':
             return \Phan\Language\Type\ArrayType::instance();
         case 'bool':
             return \Phan\Language\Type\BoolType::instance();
         case 'callable':
             return \Phan\Language\Type\CallableType::instance();
         case 'float':
             return \Phan\Language\Type\FloatType::instance();
         case 'int':
             return \Phan\Language\Type\IntType::instance();
         case 'mixed':
             return \Phan\Language\Type\MixedType::instance();
         case 'null':
             return \Phan\Language\Type\NullType::instance();
         case 'object':
             return \Phan\Language\Type\ObjectType::instance();
         case 'resource':
             return \Phan\Language\Type\ResourceType::instance();
         case 'string':
             return \Phan\Language\Type\StringType::instance();
         case 'void':
             return \Phan\Language\Type\VoidType::instance();
     }
     // If this is a type referencing the current class
     // in scope such as 'self' or 'static', return that.
     if (self::isSelfTypeString($type_name) && $context->isInClassScope()) {
         return static::fromFullyQualifiedString((string) $context->getClassFQSEN());
     }
     // Attach the context's namespace to the type name
     return self::fromNamespaceAndName($context->getNamespace() ?: '\\', $type_name);
 }
Example #11
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 visitMethodCall(Node $node) : string
 {
     if ($node->children['expr']->kind == \ast\AST_VAR) {
         if ($node->children['expr']->children['name'] instanceof Node) {
             return '';
         }
         // $var->method()
         if ($node->children['expr']->children['name'] == 'this') {
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             return (string) $this->context->getClassFQSEN();
         }
         $variable_name = $node->children['expr']->children['name'];
         if (!$this->context->getScope()->hasVariableWithName($variable_name)) {
             // Got lost, couldn't find the variable in the current scope
             // If it really isn't defined, it will be caught by the
             // undefined var error
             return '';
         }
         $variable = $this->context->getScope()->getVariableWithName($variable_name);
         // Hack - loop through the possible types of the var and assume
         // first found class is correct
         foreach ($variable->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
             $child_class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             if ($this->code_base->hasClassWithFQSEN($child_class_fqsen)) {
                 return (string) FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
             }
         }
         // Could not find name
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_PROP) {
         $prop = $node->children['expr'];
         if (!($prop->children['expr']->kind == \ast\AST_VAR && !$prop->children['expr']->children['name'] instanceof Node)) {
             return '';
         }
         // $var->prop->method()
         $var = $prop->children['expr'];
         if ($var->children['name'] == 'this') {
             // If we're not in a class scope, 'this' won't work
             if (!$this->context->isInClassScope()) {
                 Log::err(Log::ESTATIC, 'Using $this when not in object context', $this->context->getFile(), $node->lineno);
                 return '';
             }
             // Get the class in scope
             $clazz = $this->code_base->getClassByFQSEN($this->context->getClassFQSEN());
             if ($prop->children['prop'] instanceof Node) {
                 // $this->$prop->method() - too dynamic, give up
                 return '';
             }
             $property_name = $prop->children['prop'];
             if ($clazz->hasPropertyWithName($this->code_base, $property_name)) {
                 try {
                     $property = $clazz->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
                 } catch (AccessException $exception) {
                     Log::err(Log::EACCESS, $exception->getMessage(), $this->context->getFile(), $node->lineno);
                     return '';
                 }
                 // Find the first viable property type
                 foreach ($property->getUnionType()->nonGenericArrayTypes()->getTypeList() as $type) {
                     $class_fqsen = FullyQualifiedClassName::fromStringInContext((string) $type, $this->context);
                     if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
                         return (string) $class_fqsen;
                     }
                 }
             }
             // No such property was found, or none were classes
             // that could be found
             return '';
         }
         return '';
     }
     if ($node->children['expr']->kind == \ast\AST_METHOD_CALL) {
         // Get the type returned by the first method
         // call.
         $union_type = UnionType::fromNode($this->context, $this->code_base, $node->children['expr']);
         // Find the subset of types that are viable
         // classes
         $viable_class_types = $union_type->nonNativeTypes()->nonGenericArrayTypes();
         // If there are no non-native types, give up
         if ($viable_class_types->isEmpty()) {
             return '';
         }
         // Return the first non-native type in the
         // list and hope its a class
         return (string) $viable_class_types->head();
     }
     return '';
 }
Example #12
0
 /**
  * @param string $name
  * The name of the property
  *
  * @param Context $context
  * The context of the caller requesting the property
  *
  * @return Property
  * A property with the given name
  *
  * @throws IssueException
  * An exception may be thrown if the caller does not
  * have access to the given property from the given
  * context
  */
 public function getPropertyByNameInContext(CodeBase $code_base, string $name, Context $context) : Property
 {
     // Check to see if we have the property
     if (!$code_base->hasProperty($this->getFQSEN(), $name)) {
         // If we don't have the property but do have a
         // __get method, then we can create the property
         if ($this->hasMethodWithName($code_base, '__get')) {
             $property = new Property($context, $name, new UnionType(), 0);
             $property->setFQSEN(FullyQualifiedPropertyName::make($this->getFQSEN(), $name));
             $this->addProperty($code_base, $property);
         } else {
             throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$name}}"]));
         }
     }
     $property = $code_base->getProperty($this->getFQSEN(), $name);
     // If we're getting the property from outside of this
     // class and the property isn't public and we don't
     // have a getter or setter, emit an access error
     if ((!$context->hasClassFQSEN() || $context->getClassFQSEN() != $this->getFQSEN()) && !$property->isPublic() && !$this->hasMethodWithName($code_base, '__get') && !$this->hasMethodWithName($code_base, '__set')) {
         if ($property->isPrivate()) {
             throw new IssueException(Issue::fromType(Issue::AccessPropertyPrivate)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"]));
         }
         if ($property->isProtected()) {
             throw new IssueException(Issue::fromType(Issue::AccessPropertyProtected)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"]));
         }
     }
     return $property;
 }
Example #13
0
 /**
  * @param string $name
  * The name of the property
  *
  * @param Context $context
  * The context of the caller requesting the property
  *
  * @return Property
  * A property with the given name
  *
  * @throws IssueException
  * An exception may be thrown if the caller does not
  * have access to the given property from the given
  * context
  */
 public function getPropertyByNameInContext(CodeBase $code_base, string $name, Context $context) : Property
 {
     // Get the FQSEN of the property we're looking for
     $property_fqsen = FullyQualifiedPropertyName::make($this->getFQSEN(), $name);
     $property = null;
     // Figure out if we have the property
     $has_property = $code_base->hasPropertyWithFQSEN($property_fqsen);
     // Figure out if the property is accessible
     $is_property_accessible = false;
     if ($has_property) {
         $property = $code_base->getPropertyByFQSEN($property_fqsen);
         $is_remote_access = !$context->hasClassFQSEN() || $context->getClassFQSEN() != $this->getFQSEN();
         $is_property_accessible = !$is_remote_access || $property->isPublic();
     }
     // If the property exists and is accessible, return it
     if ($is_property_accessible) {
         return $property;
     }
     // Check to see if we can use a __get magic method
     if ($this->hasMethodWithName($code_base, '__get')) {
         $method = $this->getMethodByName($code_base, '__get');
         // Make sure the magic method is accessible
         if ($method->isPrivate()) {
             throw new IssueException(Issue::fromType(Issue::AccessPropertyPrivate)($context->getFile(), $context->getLineNumberStart(), [(string) $property_fqsen]));
         } else {
             if ($method->isProtected()) {
                 throw new IssueException(Issue::fromType(Issue::AccessPropertyProtected)($context->getFile(), $context->getLineNumberStart(), [(string) $property_fqsen]));
             }
         }
         $property = new Property($context, $name, $method->getUnionType(), 0);
         $property->setFQSEN($property_fqsen);
         $this->addProperty($code_base, $property);
         return $property;
     } else {
         if ($has_property) {
             // If we have a property, but its inaccessible, emit
             // an issue
             if ($property->isPrivate()) {
                 throw new IssueException(Issue::fromType(Issue::AccessPropertyPrivate)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"]));
             }
             if ($property->isProtected()) {
                 throw new IssueException(Issue::fromType(Issue::AccessPropertyProtected)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$property->getName()}"]));
             }
         }
     }
     // Check to see if missing properties are allowed
     if (Config::get()->allow_missing_properties) {
         $property = new Property($context, $name, new UnionType(), 0);
         $property->setFQSEN($property_fqsen);
         $this->addProperty($code_base, $property);
         return $property;
     }
     throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$name}}"]));
 }
Example #14
0
 /**
  * @return UnionType
  * A new UnionType with any references to 'static' resolved
  * in the given context.
  */
 public function withStaticResolvedInContext(Context $context) : UnionType
 {
     // If the context isn't in a class scope, there's nothing
     // we can do
     if (!$context->isInClassScope()) {
         return $this;
     }
     // Find the static type on the list
     $static_type = $this->getTypeSet()->find(function (Type $type) : bool {
         return $type->isStaticType();
     });
     // If we don't actually have a static type, we're all set
     if (!$static_type) {
         return $this;
     }
     // Get a copy of this UnionType to avoid having to know
     // who has copies of it out in the wild and what they're
     // hoping for.
     $union_type = clone $this;
     // Remove the static type
     $union_type->removeType($static_type);
     // Add in the class in scope
     $union_type->addType($context->getClassFQSEN()->asType());
     return $union_type;
 }