Inheritance: extends FullyQualifiedGlobalStructuralElement, use trait Phan\Memoize
示例#1
0
 public function testMethodInCodeBase()
 {
     $context = $this->contextForCode("\n                namespace A;\n                Class B {\n                    public function c() {\n                        return 42;\n                    }\n                }\n            ");
     $class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\A\\b');
     self::assertTrue($this->code_base->hasClassWithFQSEN($class_fqsen), "Class with FQSEN {$class_fqsen} not found");
     $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
     self::assertTrue($clazz->hasMethodWithName($this->code_base, 'c'), "Method with FQSEN not found");
 }
示例#2
0
 /**
  * @param Context $context
  * The context of the current execution
  *
  * @param CodeBase $code_base
  * The global code base
  *
  * @param string $class_name
  * The name we're trying to validate
  */
 public function __construct(Context $context, CodeBase $code_base, string $class_name)
 {
     $this->context = $context;
     $this->code_base = $code_base;
     $this->class_name = $class_name;
     // Compute the FQSEN based on the current context
     $this->class_fqsen = FullyQualifiedClassName::fromStringInContext($this->class_name, $this->context);
 }
示例#3
0
 public function testFullyQualifiedClassConstantName()
 {
     $this->assertFQSENEqual(FullyQualifiedClassConstantName::make(FullyQualifiedClassName::make('\\Name\\Space', 'a'), 'c'), '\\Name\\Space\\a::c');
     $this->assertFQSENEqual(FullyQualifiedClassConstantName::fromFullyQualifiedString('\\Name\\a::c'), '\\Name\\a::c');
     $this->assertFQSENEqual(FullyQualifiedClassConstantName::fromFullyQualifiedString('Name\\a::c'), '\\Name\\a::c');
     $this->assertFQSENEqual(FullyQualifiedClassConstantName::fromFullyQualifiedString('\\Name\\Space\\a::c,2'), '\\Name\\Space\\a::c,2');
     $this->assertFQSENEqual(FullyQualifiedClassConstantName::fromFullyQualifiedString('\\Name\\Space\\a,1::c,2'), '\\Name\\Space\\a,1::c,2');
     $this->assertFQSENEqual(FullyQualifiedClassConstantName::fromStringInContext('a::methodName', $this->context), '\\a::methodName');
 }
示例#4
0
文件: ContextTest.php 项目: etsy/phan
 public function testSimple()
 {
     $context = new Context();
     $context_namespace = $context->withNamespace('\\A');
     $context_class = $context_namespace->withScope(new ClassScope($context_namespace->getScope(), FullyQualifiedClassName::fromFullyQualifiedString('\\A\\B')));
     $context_method = $context_namespace->withScope(new FunctionLikeScope($context_namespace->getScope(), FullyQualifiedMethodName::fromFullyQualifiedString('\\A\\b::c')));
     $this->assertTrue(!empty($context));
     $this->assertTrue(!empty($context_namespace));
     $this->assertTrue(!empty($context_class));
     $this->assertTrue(!empty($context_method));
 }
示例#5
0
 public function testSimple()
 {
     $context = new Context();
     $context_namespace = $context->withNamespace('\\A');
     $context_class = $context_namespace->withClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\A\\B'));
     $context_method = $context_namespace->withMethodFQSEN(FullyQualifiedMethodName::fromFullyQualifiedString('\\A\\b::c'));
     $this->assertTrue(!empty($context));
     $this->assertTrue(!empty($context_namespace));
     $this->assertTrue(!empty($context_class));
     $this->assertTrue(!empty($context_method));
 }
示例#6
0
文件: Clazz.php 项目: hslatman/phan
 /**
  * @param array
  * A map from column name to value
  *
  * @return Model
  * An instance of the model derived from row data
  */
 public static function fromRow(array $row) : Clazz
 {
     $parent_fqsen = $row['parent_class_fqsen'] ? FullyQualifiedClassName::fromFullyQualifiedString($row['parent_class_fqsen']) : null;
     $interface_fqsen_list = array_map(function (string $fqsen_string) {
         return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
     }, array_filter(explode('|', $row['interface_fqsen_list'])));
     $trait_fqsen_list = array_map(function (string $fqsen_string) {
         return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
     }, array_filter(explode('|', $row['trait_fqsen_list'])));
     $clazz = new ClazzElement(unserialize(base64_decode($row['context'])), $row['name'], UnionType::fromFullyQualifiedString($row['type']), (int) $row['flags'], $parent_fqsen, $interface_fqsen_list, $trait_fqsen_list);
     return new Clazz($clazz);
 }
示例#7
0
文件: File.php 项目: tmli3b3rm4n/phan
 public static function createSchema() : Schema
 {
     $schema = new Schema('File', [new Column('file_path', Column::TYPE_STRING, true), new Column('modification_time', Column::TYPE_INT)]);
     $schema->addAssociation(new ListAssociation('FileClassFQSEN', Column::TYPE_STRING, function (File $file, array $class_fqsen_string_list) {
         $file->getFile()->setClassFQSENList(array_map(function (string $fqsen_string) {
             return FullyQualifiedClassName::fromFullyQualifiedString($fqsen_string);
         }, $class_fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FullyQualifiedClassName $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getClassFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FileMethodFQSEN', Column::TYPE_STRING, function (File $file, array $method_fqsen_string_list) {
         $file->getFile()->setMethodFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedMethodName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $method_fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getMethodFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FilePropertyFQSEN', Column::TYPE_STRING, function (File $file, array $fqsen_string_list) {
         $file->getFile()->setPropertyFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedPropertyName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getPropertyFQSENList());
     }));
     $schema->addAssociation(new ListAssociation('FileConstantFQSEN', Column::TYPE_STRING, function (File $file, array $fqsen_string_list) {
         $file->getFile()->setConstantFQSENList(array_map(function (string $fqsen_string) {
             if (false !== strpos($fqsen_string, '::')) {
                 return FullyQualifiedConstantName::fromFullyQualifiedString($fqsen_string);
             } else {
                 return FullyQualifiedFunctionName::fromFullyQualifiedString($fqsen_string);
             }
         }, $fqsen_string_list));
     }, function (File $file) {
         return array_map(function (FQSEN $fqsen) {
             return (string) $fqsen;
         }, $file->getFile()->getConstantFQSENList());
     }));
     return $schema;
 }
示例#8
0
 /**
  * Visit a node with kind `\ast\AST_CLASS`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitClass(Decl $node) : Context
 {
     if ($node->flags & \ast\flags\CLASS_ANONYMOUS) {
         $class_name = (new ContextNode($this->code_base, $this->context, $node))->getUnqualifiedNameForAnonymousClass();
     } else {
         $class_name = (string) $node->name;
     }
     assert(!empty($class_name), "Class name cannot be empty");
     $alternate_id = 0;
     // Hunt for the alternate of this class defined
     // in this file
     do {
         $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $this->context)->withAlternateId($alternate_id++);
         if (!$this->code_base->hasClassWithFQSEN($class_fqsen)) {
             throw new CodeBaseException($class_fqsen, "Can't find class {$class_fqsen} - aborting");
         }
         $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
     } while ($this->context->getProjectRelativePath() != $clazz->getContext()->getProjectRelativePath() || $this->context->getLineNumberStart() != $clazz->getContext()->getLineNumberStart());
     return $clazz->getContext()->withClassFQSEN($class_fqsen);
 }
示例#9
0
 /**
  * @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);
 }
示例#10
0
 /**
  * @return array
  * A map from alias to target
  */
 private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array
 {
     assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes');
     $map = [];
     foreach ($node->children ?? [] as $child_node) {
         $target = $child_node->children['name'];
         if (empty($child_node->children['alias'])) {
             if (($pos = strrpos($target, '\\')) !== false) {
                 $alias = substr($target, $pos + 1);
             } else {
                 $alias = $target;
             }
         } else {
             $alias = $child_node->children['alias'];
         }
         // if AST_USE does not have any flags set, then its AST_USE_ELEM
         // children will (this will be for AST_GROUP_USE)
         if ($node->flags !== 0) {
             $target_node = $node;
         } else {
             $target_node = $child_node;
         }
         if ($target_node->flags == T_FUNCTION) {
             $parts = explode('\\', $target);
             $function_name = array_pop($parts);
             $target = FullyQualifiedFunctionName::make($prefix . '\\' . implode('\\', $parts), $function_name);
         } else {
             if ($target_node->flags == T_CONST) {
                 $parts = explode('\\', $target);
                 $name = array_pop($parts);
                 $target = FullyQualifiedGlobalConstantName::make($prefix . '\\' . implode('\\', $parts), $name);
             } else {
                 $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target);
             }
         }
         $map[$alias] = [$target_node->flags, $target];
     }
     return $map;
 }
示例#11
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
 {
     // Get the name of the class
     $class_name = $node->children['class']->children['name'];
     $clazz = null;
     // If we can't figure out the class name (which happens
     // from time to time), then give up
     if (!empty($class_name)) {
         $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $this->context);
         // Check to see if the class actually exists
         if ($this->code_base->hasClassWithFQSEN($class_fqsen)) {
             $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
         } else {
             Log::err(Log::EUNDEF, "call to method on undeclared class {$class_name}", $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 ($clazz) {
             $variable->setUnionType($clazz->getUnionType());
         }
         $this->context->addScopeVariable($variable);
     }
     return $this->context;
 }
示例#12
0
 /**
  * @param CodeBase $code_base
  * A reference to the entire code base in which this
  * context exists
  *
  * @param ReflectionClass $class
  * A reflection class representing a builtin class.
  *
  * @return Clazz
  * A Class structural element representing the given named
  * builtin.
  */
 public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz
 {
     // Build a set of flags based on the constitution
     // of the built-in class
     $flags = 0;
     if ($class->isFinal()) {
         $flags = \ast\flags\CLASS_FINAL;
     } elseif ($class->isInterface()) {
         $flags = \ast\flags\CLASS_INTERFACE;
     } elseif ($class->isTrait()) {
         $flags = \ast\flags\CLASS_TRAIT;
     }
     if ($class->isAbstract()) {
         $flags |= \ast\flags\CLASS_ABSTRACT;
     }
     $context = new Context();
     // Build a base class element
     $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags);
     $clazz->setFQSEN(FullyQualifiedClassName::fromStringInContext($class->getName(), $context));
     // If this class has a parent class, add it to the
     // class info
     if ($parent_class = $class->getParentClass()) {
         $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName());
         $clazz->setParentClassFQSEN($parent_class_fqsen);
     }
     foreach ($class->getDefaultProperties() as $name => $value) {
         // TODO: whats going on here?
         $reflection_property = new \ReflectionProperty($class->getName(), $name);
         $property_context = $context->withClassFQSEN($clazz->getFQSEN());
         $property = new Property($property_context, $name, Type::fromObject($value)->asUnionType(), 0);
         $property->setFQSEN(FullyQualifiedPropertyName::make($clazz->getFQSEN(), $name));
         $clazz->addProperty($code_base, $property);
     }
     foreach ($class->getInterfaceNames() as $name) {
         $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getTraitNames() as $name) {
         $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getConstants() as $name => $value) {
         $constant = new ClassConstant($context, $name, Type::fromObject($value)->asUnionType(), 0);
         $constant->setFQSEN(FullyQualifiedClassConstantName::make($clazz->getFQSEN(), $name));
         $clazz->addConstant($code_base, $constant);
     }
     foreach ($class->getMethods() as $reflection_method) {
         $method_list = FunctionFactory::methodListFromReflectionClassAndMethod($context->withClassFQSEN($clazz->getFQSEN()), $code_base, $class, $reflection_method);
         foreach ($method_list as $method) {
             $clazz->addMethod($code_base, $method);
         }
     }
     return $clazz;
 }
示例#13
0
 public function unserialize($serialized)
 {
     list($file_ref, $serialized) = explode('^', $serialized);
     parent::unserialize($file_ref);
     list($namespace, $is_conditional, $class_fqsen, $method_fqsen, $closure_fqsen) = explode('|', $serialized);
     $this->namespace = $namespace;
     $this->is_conditional = (bool) $is_conditional;
     $this->class_fqsen = $class_fqsen ? FullyQualifiedClassName::fromFullyQualifiedString($class_fqsen) : null;
     $this->method_fqsen = $method_fqsen ? FullyQualifiedMethodName::fromFullyQualifiedString($method_fqsen) : null;
     $this->closure_fqsen = $closure_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($closure_fqsen) : null;
 }
示例#14
0
文件: Clazz.php 项目: hslatman/phan
 /**
  * @return FQSEN
  */
 public function getFQSEN() : FullyQualifiedClassName
 {
     // Allow overrides
     if ($this->fqsen) {
         return $this->fqsen;
     }
     return FullyQualifiedClassName::fromStringInContext($this->getName(), $this->getContext());
 }
示例#15
0
 /**
  * @return void
  */
 public function unserialize($serialized)
 {
     list($file_ref, $serialized) = explode('^', $serialized);
     parent::unserialize($file_ref);
     list($namespace, $class_fqsen, $method_fqsen, $closure_fqsen) = explode('|', $serialized);
     $this->namespace = $namespace;
     $this->class_fqsen = $class_fqsen ? FullyQualifiedClassName::fromFullyQualifiedString($class_fqsen) : null;
     // Determine if we have a method or a function
     if (false === strpos($method_fqsen, '::')) {
         $this->method_fqsen = $method_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($method_fqsen) : null;
     } else {
         $this->method_fqsen = $method_fqsen ? FullyQualifiedMethodName::fromFullyQualifiedString($method_fqsen) : null;
     }
     $this->closure_fqsen = $closure_fqsen ? FullyQualifiedFunctionName::fromFullyQualifiedString($closure_fqsen) : null;
 }
示例#16
0
文件: AST.php 项目: tmli3b3rm4n/phan
 /**
  * @param Node $node
  * The node that has a reference to a class
  *
  * @param Context $context
  * The context in which we found the node
  *
  * @param CodeBase $code_base
  * The global code base holding all state
  *
  * @param bool $validate_class_name
  * If true, we'll validate that the name of the class
  * is valid.
  *
  * @return Clazz
  * The class being referenced in the given node in
  * the given context
  *
  * @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 referenced
  * class
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public static function classFromNodeInContext(Node $node, Context $context, CodeBase $code_base, bool $validate_class_name = true) : Clazz
 {
     // Figure out the name of the class
     $class_name = self::classNameFromNode($context, $code_base, $node, $validate_class_name);
     // If we can't figure out the class name (which happens
     // from time to time), then give up
     if (empty($class_name)) {
         throw new NodeException($node, 'Could not find class name');
     }
     $class_fqsen = FullyQualifiedClassName::fromStringInContext($class_name, $context);
     // Check to see if the class actually exists
     if (!$code_base->hasClassWithFQSEN($class_fqsen)) {
         throw new CodeBaseException("Can't find class {$class_fqsen}");
     }
     $class = $code_base->getClassByFQSEN($class_fqsen);
     return $class;
 }
示例#17
0
 /**
  * Visit a node with kind `\ast\AST_METHOD_CALL`
  *
  * @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 UnionType
  * The set of types that are possibly produced by the
  * given node
  */
 public function visitMethodCall(Node $node) : UnionType
 {
     $class_name = AST::classNameFromNode($this->context, $this->code_base, $node);
     if (empty($class_name)) {
         return new UnionType();
     }
     $class_fqsen = FullyQualifiedClassName::fromstringInContext($class_name, $this->context);
     assert($this->code_base->hasClassWithFQSEN($class_fqsen), "Class {$class_fqsen} must exist");
     $clazz = $this->code_base->getClassByFQSEN($class_fqsen);
     $method_name = $node->children['method'];
     // Give up on any complicated nonsense where the
     // method name is a variable such as in
     // `$variable->$function_name()`.
     if ($method_name instanceof Node) {
         return new UnionType();
     }
     // Method names can some times turn up being
     // other method calls.
     assert(is_string($method_name), "Method name must be a string. Something else given.");
     if (!$clazz->hasMethodWithName($this->code_base, $method_name)) {
         Log::err(Log::EUNDEF, "call to undeclared method {$class_fqsen}->{$method_name}()", $this->context->getFile(), $node->lineno);
         return new UnionType();
     }
     $method = $clazz->getMethodByNameInContext($this->code_base, $method_name, $this->context);
     return $method->getUnionType();
 }
示例#18
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 '';
 }
示例#19
0
文件: Clazz.php 项目: etsy/phan
 /**
  * @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->isInClassScope() || !$context->getClassInScope($code_base)->getUnionType()->canCastToExpandedUnionType($this->getUnionType(), $code_base);
         $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_fqsen);
         $this->addProperty($code_base, $property, new None());
         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
     // or we're stdclass
     if (Config::get()->allow_missing_properties || $this->getFQSEN() == FullyQualifiedClassName::getStdClassFQSEN()) {
         $property = new Property($context, $name, new UnionType(), 0, $property_fqsen);
         $this->addProperty($code_base, $property, new None());
         return $property;
     }
     throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($context->getFile(), $context->getLineNumberStart(), ["{$this->getFQSEN()}::\${$name}}"]));
 }
示例#20
0
文件: Context.php 项目: etsy/phan
 /**
  * @return FullyQualifiedGlobalStructuralElement
  * The namespace mapped name for the given flags and name
  */
 public function getNamespaceMapFor(int $flags, string $name) : FullyQualifiedGlobalStructuralElement
 {
     $name = strtolower($name);
     // Look for the mapping on the part before a
     // slash
     $name_parts = explode('\\', $name, 2);
     $suffix = '';
     if (count($name_parts) > 1) {
         $name = $name_parts[0];
         $suffix = $name_parts[1];
     }
     assert(!empty($this->namespace_map[$flags][$name]), "No namespace defined for name");
     assert($this->namespace_map[$flags][$name] instanceof FQSEN, "Namespace map was not an FQSEN");
     $fqsen = $this->namespace_map[$flags][$name];
     if (!$suffix) {
         return $fqsen;
     }
     switch ($flags) {
         case \ast\flags\USE_NORMAL:
             return FullyQualifiedClassName::fromFullyQualifiedString((string) $fqsen . '\\' . $suffix);
         case \ast\flags\USE_FUNCTION:
             return FullyQualifiedFunctionName::fromFullyQualifiedString((string) $fqsen . '\\' . $suffix);
     }
     assert(false, "Unknown flag {$flags}");
     return $fqsen;
 }
示例#21
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();
 }
示例#22
0
文件: Clazz.php 项目: etsy/phan
 /**
  * @param CodeBase $code_base
  * A reference to the entire code base in which this
  * context exists
  *
  * @param ReflectionClass $class
  * A reflection class representing a builtin class.
  *
  * @return Clazz
  * A Class structural element representing the given named
  * builtin.
  */
 public static function fromReflectionClass(CodeBase $code_base, \ReflectionClass $class) : Clazz
 {
     // Build a set of flags based on the constitution
     // of the built-in class
     $flags = 0;
     if ($class->isFinal()) {
         $flags = \ast\flags\CLASS_FINAL;
     } elseif ($class->isInterface()) {
         $flags = \ast\flags\CLASS_INTERFACE;
     } elseif ($class->isTrait()) {
         $flags = \ast\flags\CLASS_TRAIT;
     }
     if ($class->isAbstract()) {
         $flags |= \ast\flags\CLASS_ABSTRACT;
     }
     $context = new Context();
     $class_fqsen = FullyQualifiedClassName::fromStringInContext($class->getName(), $context);
     // Build a base class element
     $clazz = new Clazz($context, $class->getName(), UnionType::fromStringInContext($class->getName(), $context), $flags, $class_fqsen);
     // If this class has a parent class, add it to the
     // class info
     if ($parent_class = $class->getParentClass()) {
         $parent_class_fqsen = FullyQualifiedClassName::fromFullyQualifiedString('\\' . $parent_class->getName());
         $parent_type = $parent_class_fqsen->asType();
         $clazz->setParentType($parent_type);
     }
     // n.b.: public properties on internal classes don't get
     //       listed via reflection until they're set unless
     //       they have a default value. Therefore, we don't
     //       bother iterating over `$class->getProperties()`
     //       `$class->getStaticProperties()`.
     foreach ($class->getDefaultProperties() as $name => $value) {
         $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $name);
         $property = new Property($property_context, $name, Type::fromObject($value)->asUnionType(), 0, $property_fqsen);
         $clazz->addProperty($code_base, $property, new None());
     }
     foreach (UnionType::internalPropertyMapForClassName($clazz->getName()) as $property_name => $property_type_string) {
         // An asterisk indicates that the class supports
         // dynamic properties
         if ($property_name === '*') {
             $clazz->setHasDynamicProperties(true);
             continue;
         }
         $property_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $property_type = UnionType::fromStringInContext($property_type_string, new Context());
         $property_fqsen = FullyQualifiedPropertyName::make($clazz->getFQSEN(), $property_name);
         $property = new Property($property_context, $property_name, $property_type, 0, $property_fqsen);
         $clazz->addProperty($code_base, $property, new None());
     }
     foreach ($class->getInterfaceNames() as $name) {
         $clazz->addInterfaceClassFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getTraitNames() as $name) {
         $clazz->addTraitFQSEN(FullyQualifiedClassName::fromFullyQualifiedString('\\' . $name));
     }
     foreach ($class->getConstants() as $name => $value) {
         $constant_fqsen = FullyQualifiedClassConstantName::make($clazz->getFQSEN(), $name);
         $constant = new ClassConstant($context, $name, Type::fromObject($value)->asUnionType(), 0, $constant_fqsen);
         $clazz->addConstant($code_base, $constant);
     }
     foreach ($class->getMethods() as $reflection_method) {
         $method_context = $context->withScope(new ClassScope(new GlobalScope(), $clazz->getFQSEN()));
         $method_list = FunctionFactory::methodListFromReflectionClassAndMethod($method_context, $code_base, $class, $reflection_method);
         foreach ($method_list as $method) {
             $clazz->addMethod($code_base, $method, new None());
         }
     }
     return $clazz;
 }
示例#23
0
 /**
  * @return array
  * A map from alias to target
  */
 private function aliasTargetMapFromUseNode(Node $node, string $prefix = '') : array
 {
     assert($node->kind == \ast\AST_USE, 'Method takes AST_USE nodes');
     $map = [];
     foreach ($node->children ?? [] as $child_node) {
         $target = $child_node->children['name'];
         if (empty($child_node->children['alias'])) {
             if (($pos = strrpos($target, '\\')) !== false) {
                 $alias = substr($target, $pos + 1);
             } else {
                 $alias = $target;
             }
         } else {
             $alias = $child_node->children['alias'];
         }
         if ($node->flags == T_FUNCTION) {
             $parts = explode('\\', $target);
             $function_name = array_pop($parts);
             $target = FullyQualifiedFunctionName::make(implode('\\', $parts), $function_name);
         } else {
             $target = FullyQualifiedClassName::fromFullyQualifiedString($prefix . '\\' . $target);
         }
         $map[$alias] = [$child_node->flags, $target];
     }
     return $map;
 }
示例#24
0
 /**
  * @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");
     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) {
             Issue::maybeEmitInstance($this->code_base, $this->context, $exception->getIssueInstance());
             return $this->context;
         }
         if (!$this->right_type->canCastToExpandedUnionType($property->getUnionType(), $this->code_base)) {
             $this->emitIssue(Issue::TypeMismatchProperty, $node->lineno ?? 0, (string) $this->right_type, "{$clazz->getFQSEN()}::{$property->getName()}", (string) $property->getUnionType());
             return $this->context;
         } else {
             // If we're assigning to an array element then we don't
             // know what the constitutation of the parameter is
             // outside of the scope of this assignment, so we add to
             // its union type rather than replace it.
             if ($this->is_dim_assignment) {
                 $property->getUnionType()->addUnionType($this->right_type);
             }
         }
         // After having checked it, add this type to it
         $property->getUnionType()->addUnionType($this->right_type);
         return $this->context;
     }
     $std_class_fqsen = FullyQualifiedClassName::getStdClassFQSEN();
     if (Config::get()->allow_missing_properties || !empty($class_list) && $class_list[0]->getFQSEN() == $std_class_fqsen) {
         try {
             // Create the property
             $property = (new ContextNode($this->code_base, $this->context, $node))->getOrCreateProperty($property_name);
             $property->getUnionType()->addUnionType($this->right_type);
         } catch (\Exception $exception) {
             // swallow it
         }
     } elseif (!empty($class_list)) {
         $this->emitIssue(Issue::UndeclaredProperty, $node->lineno ?? 0, "{$class_list[0]->getFQSEN()}->{$property_name}");
     } else {
         // If we hit this part, we couldn't figure out
         // the class, so we ignore the issue
     }
     return $this->context;
 }
示例#25
0
 /**
  * Visit a node with kind `\ast\AST_USE_TRAIT`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitUseTrait(Node $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     $trait_fqsen_string_list = (new ContextNode($this->code_base, $this->context, $node->children['traits']))->getQualifiedNameList();
     // Add each trait to the class
     foreach ($trait_fqsen_string_list as $trait_fqsen_string) {
         $trait_fqsen = FullyQualifiedClassName::fromStringInContext($trait_fqsen_string, $this->context);
         $clazz->addTraitFQSEN($trait_fqsen);
     }
     return $this->context;
 }
示例#26
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 '';
 }
示例#27
0
文件: Type.php 项目: ablyler/phan
 /**
  * @return FQSEN
  * A fully-qualified structural element name derived
  * from this type
  */
 public function asFQSEN() : FQSEN
 {
     return FullyQualifiedClassName::fromType($this);
 }
示例#28
0
 /**
  * @param string|Node $property_name
  * The name of the property we're looking up
  *
  * @return Property
  * A variable in scope or a new variable
  *
  * @throws NodeException
  * An exception is thrown if we can't understand the node
  *
  * @throws IssueException
  * An exception is thrown if we can't find the given
  * class or if we don't have access to the property (its
  * private or protected).
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  *
  * @throws UnanalyzableException
  * An exception is thrown if we hit a construct in which
  * we can't determine if the property exists or not
  */
 public function getProperty($property_name) : Property
 {
     $property_name = $this->node->children['prop'];
     // Give up for things like C::$prop_name
     if (!is_string($property_name)) {
         throw new NodeException($this->node, "Cannot figure out non-string property name");
     }
     $class_fqsen = null;
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $this->node->children['expr'] ?? $this->node->children['class']))->getClassList(true);
     } catch (CodeBaseException $exception) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($this->context->getFile(), $this->node->lineno ?? 0, ["{$exception->getFQSEN()}->{$property_name}"]));
     }
     foreach ($class_list as $i => $class) {
         $class_fqsen = $class->getFQSEN();
         // Keep hunting if this class doesn't have the given
         // property
         if (!$class->hasPropertyWithName($this->code_base, $property_name)) {
             // If there's a getter on properties then all
             // bets are off.
             if ($class->hasGetMethod($this->code_base)) {
                 throw new UnanalyzableException($this->node, "Can't determine if property {$property_name} exists in class {$class->getFQSEN()} with __get defined");
             }
             continue;
         }
         $property = $class->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
         if ($property->isDeprecated()) {
             throw new IssueException(Issue::fromType(Issue::DeprecatedProperty)($this->context->getFile(), $this->node->lineno ?? 0, [(string) $property->getFQSEN(), $property->getFileRef()->getFile(), $property->getFileRef()->getLineNumberStart()]));
         }
         return $property;
     }
     $std_class_fqsen = FullyQualifiedClassName::getStdClassFQSEN();
     // If missing properties are cool, create it on
     // the first class we found
     if ($class_fqsen && $class_fqsen === $std_class_fqsen || Config::get()->allow_missing_properties) {
         if (count($class_list) > 0) {
             $class = $class_list[0];
             return $class->getPropertyByNameInContext($this->code_base, $property_name, $this->context);
         }
     }
     // If the class isn't found, we'll get the message elsewhere
     if ($class_fqsen) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredProperty)($this->context->getFile(), $this->node->lineno ?? 0, ["{$class_fqsen}->{$property_name}"]));
     }
     throw new NodeException($this->node, "Cannot figure out property from {$this->context}");
 }