Example #1
0
 /**
  * Visit a node with kind `\ast\AST_PROP_DECL`
  *
  * @param Node $node
  * A node to parse
  *
  * @return Context
  * A new or an unchanged context resulting from
  * parsing the node
  */
 public function visitPropDecl(Node $node) : Context
 {
     // Bomb out if we're not in a class context
     $clazz = $this->getContextClass();
     // Get a comment on the property declaration
     $comment = Comment::fromStringInContext($node->children[0]->docComment ?? '', $this->context);
     foreach ($node->children ?? [] as $i => $child_node) {
         // Ignore children which are not property elements
         if (!$child_node || $child_node->kind != \ast\AST_PROP_ELEM) {
             continue;
         }
         // If something goes wrong will getting the type of
         // a property, we'll store it as a future union
         // type and try to figure it out later
         $future_union_type = null;
         try {
             // Get the type of the default
             $union_type = UnionType::fromNode($this->context, $this->code_base, $child_node->children['default'], false);
         } catch (IssueException $exception) {
             $future_union_type = new FutureUnionType($this->code_base, $this->context, $child_node->children['default']);
             $union_type = new UnionType();
         }
         // Don't set 'null' as the type if thats the default
         // given that its the default default.
         if ($union_type->isType(NullType::instance())) {
             $union_type = new UnionType();
         }
         $property_name = $child_node->children['name'];
         assert(is_string($property_name), 'Property name must be a string. ' . 'Got ' . print_r($property_name, true) . ' at ' . $this->context);
         $property = new Property(clone $this->context->withLineNumberStart($child_node->lineno ?? 0), is_string($child_node->children['name']) ? $child_node->children['name'] : '_error_', $union_type, $node->flags ?? 0);
         $property->setFQSEN(FullyQualifiedPropertyName::make($clazz->getFQSEN(), $property->getName()));
         // Add the property to the class
         $clazz->addProperty($this->code_base, $property);
         $property->setSuppressIssueList($comment->getSuppressIssueList());
         // Look for any @var declarations
         if ($variable = $comment->getVariableList()[$i] ?? null) {
             if ((string) $union_type != 'null' && !$union_type->canCastToUnionType($variable->getUnionType())) {
                 $this->emitIssue(Issue::TypeMismatchProperty, $child_node->lineno ?? 0, (string) $union_type, (string) $property->getFQSEN(), (string) $variable->getUnionType());
             }
             // Set the declared type to the doc-comment type and add
             // |null if the default value is null
             $property->getUnionType()->addUnionType($variable->getUnionType());
         }
         // Wait until after we've added the (at)var type
         // before setting the future so that calling
         // $property->getUnionType() doesn't force the
         // future to be reified.
         if (!empty($future_union_type)) {
             $property->setFutureUnionType($future_union_type);
         }
     }
     return $this->context;
 }
Example #2
0
 /**
  * @param Node $node
  * A node that has a reference to a variable
  *
  * @param Context $context
  * The context in which we found the reference
  *
  * @param CodeBase $code_base
  *
  * @return Variable
  * A variable in scope or a new variable
  *
  * @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
  * class
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public static function getOrCreatePropertyFromNodeInContext(string $property_name, Node $node, Context $context, CodeBase $code_base) : Property
 {
     assert(is_string($property_name), 'Property name must be a string. ' . 'Got ' . print_r($property_name, true) . ' at ' . $context);
     // Figure out the class we're looking the property
     // up for
     $clazz = self::classFromNodeInContext($node, $context, $code_base);
     // Return it if the property exists on the class
     if ($clazz->hasPropertyWithName($code_base, $property_name)) {
         return $clazz->getPropertyByNameInContext($code_base, $property_name, $context);
     }
     $flags = 0;
     if ($node->kind == \ast\AST_STATIC_PROP) {
         $flags |= \ast\flags\MODIFIER_STATIC;
     }
     // Otherwise, we'll create it
     $property = new Property($context, $property_name, new UnionType(), $flags);
     $property->setFQSEN(FullyQualifiedPropertyName::make($clazz->getFQSEN(), $property_name));
     $clazz->addProperty($code_base, $property);
     return $property;
 }
Example #3
0
 /**
  * @return Property
  * A variable in scope or a new variable
  *
  * @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
  * class
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public function getOrCreateProperty(string $property_name) : Property
 {
     try {
         return $this->getProperty($property_name);
     } catch (IssueException $exception) {
         // Ignore it, because we'll create our own
         // property
     } catch (UnanalyzableException $exception) {
         // Ignore it, because we'll create our own
         // property
     }
     try {
         $class_list = (new ContextNode($this->code_base, $this->context, $this->node))->getClassList();
     } catch (CodeBaseException $exception) {
         throw new IssueException(Issue::fromType(Issue::UndeclaredClassReference)($this->context->getFile(), $this->node->lineno ?? 0, [$exception->getFQSEN()]));
     }
     if (empty($class_list)) {
         throw new UnanalyzableException($this->node, "Could not get class name from node");
     }
     $class = array_values($class_list)[0];
     $flags = 0;
     if ($this->node->kind == \ast\AST_STATIC_PROP) {
         $flags |= \ast\flags\MODIFIER_STATIC;
     }
     // Otherwise, we'll create it
     $property = new Property($this->context, $property_name, new UnionType(), $flags);
     $property->setFQSEN(FullyQualifiedPropertyName::make($class->getFQSEN(), $property_name));
     $class->addProperty($this->code_base, $property);
     return $property;
 }
Example #4
0
File: Clazz.php Project: etsy/phan
 /**
  * Add a property to this class
  *
  * @param CodeBase $code_base
  * A reference to the code base in which the ancestor exists
  *
  * @param Property $property
  * The property to copy onto this class
  *
  * @param Option<Type>|None $type_option
  * A possibly defined type used to define template
  * parameter types when importing the property
  *
  * @return void
  */
 public function addProperty(CodeBase $code_base, Property $property, $type_option)
 {
     // Ignore properties we already have
     if ($this->hasPropertyWithName($code_base, $property->getName())) {
         return;
     }
     $property_fqsen = FullyQualifiedPropertyName::make($this->getFQSEN(), $property->getName());
     if ($property->getFQSEN() !== $property_fqsen) {
         $property = clone $property;
         $property->setDefiningFQSEN($property->getFQSEN());
         $property->setFQSEN($property_fqsen);
         try {
             // If we have a parent type defined, map the property's
             // type through it
             if ($type_option->isDefined() && $property->getUnionType()->hasTemplateType()) {
                 $property->setUnionType($property->getUnionType()->withTemplateParameterTypeMap($type_option->get()->getTemplateParameterTypeMap($code_base)));
             }
         } catch (IssueException $exception) {
             Issue::maybeEmitInstance($code_base, $property->getContext(), $exception->getIssueInstance());
         }
     }
     $code_base->addProperty($property);
 }
Example #5
0
 /**
  * @return Property 
  * A variable in scope or a new variable
  *
  * @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
  * class
  *
  * @throws TypeException
  * An exception may be thrown if the only viable candidate
  * is a non-class type.
  */
 public function getOrCreateProperty(string $property_name) : Property
 {
     try {
         return $this->getProperty($property_name);
     } catch (CodeBaseException $exception) {
         // Ignore it, because we'll create our own
         // property
     } catch (UnanalyzableException $exception) {
         // Ignore it, because we'll create our own
         // property
     }
     // Figure out the class we're looking the property
     // up for
     $class = $this->getClass();
     $flags = 0;
     if ($this->node->kind == \ast\AST_STATIC_PROP) {
         $flags |= \ast\flags\MODIFIER_STATIC;
     }
     // Otherwise, we'll create it
     $property = new Property($this->context, $property_name, new UnionType(), $flags);
     $property->setFQSEN(FullyQualifiedPropertyName::make($class->getFQSEN(), $property_name));
     $class->addProperty($this->code_base, $property);
     return $property;
 }
Example #6
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 #7
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 #8
0
 /**
  * @return array
  * Get a map from column name to row values for
  * this instance
  */
 public function toRow() : array
 {
     return ['scope_name' => $this->primaryKeyValue(), 'fqsen' => (string) $this->property->getFQSEN(), 'name' => (string) $this->property->getName(), 'type' => (string) $this->property->getUnionType(), 'flags' => $this->property->getFlags(), 'context' => base64_encode(serialize($this->property->getContext())), 'is_deprecated' => $this->property->isDeprecated()];
 }
Example #9
0
 /**
  * @return void
  */
 public function addProperty(Property $property)
 {
     $this->property_map[$property->getFQSEN()->getNameWithAlternateId()] = $property;
 }
Example #10
0
 /**
  * @param Property $property
  * Any property
  *
  * @param FullyQualifiedClassName $fqsen
  * The FQSEN to index the property by
  *
  * @return null
  */
 public function addPropertyInScope(Property $property, FullyQualifiedClassName $fqsen)
 {
     $name = $property->getFQSEN()->getNameWithAlternateId();
     $this->property_map[(string) $fqsen][$name] = $property;
     // For elements that aren't internal PHP classes
     if (!$property->getContext()->isInternal()) {
         // Associate the element with the file it was found in
         $this->getFileByPath($property->getContext()->getFile())->addPropertyFQSEN($property->getFQSEN());
     }
 }