/** * 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; }
/** * 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); }
/** * @return void */ public function addProperty(CodeBase $code_base, Property $property) { // 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->setFQSEN($property_fqsen); } $code_base->addProperty($property); }
/** * @return void */ public function addProperty(CodeBase $code_base, Property $property) { if (!$this->hasPropertyWithName($code_base, $property->getName())) { $code_base->addPropertyInScope($property, $this->getFQSEN()); } }
/** * @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()]; }