Skip to content

Add support for generating enumsynopsis for Enums #14369

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 131 additions & 29 deletions build/gen_stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -2217,10 +2217,10 @@ public function enterNode(Node $expr)
$isUnknownConstValue = false;

$evaluator = new ConstExprEvaluator(
static function (Expr $expr) use ($allConstInfos, &$isUnknownConstValue) {
// $expr is a ConstFetch with a name of a C macro here
function (Expr $expr) use ($allConstInfos, &$isUnknownConstValue) {
// $expr is a ConstFetch with a name of a C macro here. Class constants are not supported yet
if (!$expr instanceof Expr\ConstFetch) {
throw new Exception($this->getVariableTypeName() . " " . $this->name->__toString() . " has an unsupported value");
throw new Exception("Cannot evaluate expression");
}

$constName = $expr->name->__toString();
Expand Down Expand Up @@ -2379,7 +2379,7 @@ abstract protected function getFieldSynopsisDefaultLinkend(): string;
abstract protected function getFieldSynopsisName(): string;

/** @param array<string, ConstInfo> $allConstInfos */
abstract protected function getFieldSynopsisValueString(array $allConstInfos): ?string;
abstract public function getFieldSynopsisValueString(array $allConstInfos): ?string;

abstract public function discardInfoForOldPhpVersions(?int $minimumPhpVersionIdCompatibility): void;

Expand Down Expand Up @@ -2604,7 +2604,7 @@ protected function getFieldSynopsisName(): string
}

/** @param array<string, ConstInfo> $allConstInfos */
protected function getFieldSynopsisValueString(array $allConstInfos): ?string
public function getFieldSynopsisValueString(array $allConstInfos): ?string
{
$value = EvaluatedValue::createFromExpression($this->value, null, $this->cValue, $allConstInfos);
if ($value->isUnknownConstValue) {
Expand Down Expand Up @@ -2939,7 +2939,7 @@ protected function getFieldSynopsisName(): string
}

/** @param array<string, ConstInfo> $allConstInfos */
protected function getFieldSynopsisValueString(array $allConstInfos): ?string
public function getFieldSynopsisValueString(array $allConstInfos): ?string
{
return $this->defaultValueString;
}
Expand Down Expand Up @@ -3061,10 +3061,12 @@ public function __clone()
class EnumCaseInfo {
public string $name;
public ?Expr $value;
public ?string $valueString;

public function __construct(string $name, ?Expr $value) {
public function __construct(string $name, ?Expr $value, ?string $valueString) {
$this->name = $name;
$this->value = $value;
$this->valueString = $valueString;
}

/** @param array<string, ConstInfo> $allConstInfos */
Expand All @@ -3082,6 +3084,51 @@ public function getDeclaration(array $allConstInfos): string {

return $code;
}

/** @param array<string, ConstInfo> $allConstInfos */
public function getEnumItemElement(DOMDocument $doc, array $allConstInfos): DOMElement
{
$enumItemElement = $doc->createElement("enumitem");

$enumItemElement->appendChild(new DOMText("\n "));
$enumIdentifierElement = $doc->createElement("enumidentifier");
$enumIdentifierElement->appendChild(new DOMText($this->name));
$enumItemElement->appendChild($enumIdentifierElement);

$valueString = $this->getEnumValueString($allConstInfos);
if ($valueString) {
$enumValueElement = $doc->createElement("enumvalue");
$enumItemElement->appendChild(new DOMText("\n "));
$initializerElement = $doc->createElement("initializer", $valueString);
$enumValueElement->appendChild($initializerElement);
$enumItemElement->appendChild($enumValueElement);
}

$enumItemElement->appendChild(new DOMText("\n "));

return $enumItemElement;
}

/** @param array<string, ConstInfo> $allConstInfos */
protected function getEnumValueString(array $allConstInfos): ?string
{
if ($this->value === null) {
return null;
};

$value = EvaluatedValue::createFromExpression($this->value, null, null, $allConstInfos);
if ($value->isUnknownConstValue) {
return null;
}

if ($value->originatingConsts) {
return implode("\n", array_map(function (ConstInfo $const) use ($allConstInfos) {
return $const->getFieldSynopsisValueString($allConstInfos);
}, $value->originatingConsts));
}

return $this->valueString;
}
}

class AttributeInfo {
Expand Down Expand Up @@ -3468,17 +3515,36 @@ public function getClassSynopsisDocument(array $classMap, array $allConstInfos):
* @param array<string, ConstInfo> $allConstInfos
*/
public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array $allConstInfos): ?DOMElement {
$isEnum = $this->type === "enum";

$classSynopsis = $doc->createElement($isEnum ? "enumsynopsis" : "classsynopsis");
$synopsisInfoName = $isEnum ? "synopsisinfo" : "classsynopsisinfo";

$classSynopsis = $doc->createElement("classsynopsis");
$classSynopsis->setAttribute("class", $this->type === "interface" ? "interface" : "class");
if (!$isEnum) {
$classSynopsis->setAttribute("class", $this->type);
}

$exceptionOverride = $this->type === "class" && $this->isException($classMap) ? "exception" : null;
$classSynopsis->appendChild(new DOMText("\n "));

$ooElement = self::createOoElement($doc, $this, $exceptionOverride, true, null, 4);
if (!$ooElement) {
return null;
if ($ooElement) {
$classSynopsis->appendChild($ooElement);
}

if ($isEnum) {
$enumNameElement = $doc->createElement("enumname");
$enumNameElement->appendChild(new DOMText($this->name->toString()));
$classSynopsis->appendChild($enumNameElement);

if ($this->enumBackingType) {
$classSynopsis->appendChild(new DOMText("\n "));
$enumNameElement = $doc->createElement("modifier");
$enumNameElement->setAttribute("role", "enum_backing_type");
$enumNameElement->appendChild(new DOMText($this->enumBackingType->name));
$classSynopsis->appendChild($enumNameElement);
}
}
$classSynopsis->appendChild(new DOMText("\n "));
$classSynopsis->appendChild($ooElement);

foreach ($this->extends as $k => $parent) {
$parentInfo = $classMap[$parent->toString()] ?? null;
Expand All @@ -3502,6 +3568,11 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array
$classSynopsis->appendChild($ooElement);
}

// Enums implicitly implement either UnitEnum or BackEnum. This way inherited methods can be displayed.
if ($isEnum) {
$this->implements[] = new Name\FullyQualified($this->enumBackingType ? "BackedEnum" : "UnitEnum");
}

foreach ($this->implements as $k => $interface) {
$interfaceInfo = $classMap[$interface->toString()] ?? null;
if (!$interfaceInfo) {
Expand Down Expand Up @@ -3535,13 +3606,14 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array
$doc,
$classSynopsis,
$parentsWithInheritedConstants,
$synopsisInfoName,
"&Constants;",
"&InheritedConstants;"
);

if (!empty($this->constInfos)) {
$classSynopsis->appendChild(new DOMText("\n\n "));
$classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Constants;");
$classSynopsisInfo = $doc->createElement($synopsisInfoName, "&Constants;");
$classSynopsisInfo->setAttribute("role", "comment");
$classSynopsis->appendChild($classSynopsisInfo);

Expand All @@ -3552,9 +3624,22 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array
}
}

if (!empty($this->enumCaseInfos)) {
$classSynopsis->appendChild(new DOMText("\n\n "));
$classSynopsisInfo = $doc->createElement($synopsisInfoName, "&EnumCases;");
$classSynopsisInfo->setAttribute("role", "comment");
$classSynopsis->appendChild($classSynopsisInfo);

foreach ($this->enumCaseInfos as $enumCaseInfo) {
$classSynopsis->appendChild(new DOMText("\n "));
$enumItemElement = $enumCaseInfo->getEnumItemElement($doc, $allConstInfos);
$classSynopsis->appendChild($enumItemElement);
}
}

if (!empty($this->propertyInfos)) {
$classSynopsis->appendChild(new DOMText("\n\n "));
$classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Properties;");
$classSynopsisInfo = $doc->createElement($synopsisInfoName, "&Properties;");
$classSynopsisInfo->setAttribute("role", "comment");
$classSynopsis->appendChild($classSynopsisInfo);

Expand All @@ -3569,13 +3654,14 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array
$doc,
$classSynopsis,
$parentsWithInheritedProperties,
$synopsisInfoName,
"&Properties;",
"&InheritedProperties;"
);

if (!empty($this->funcInfos)) {
$classSynopsis->appendChild(new DOMText("\n\n "));
$classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Methods;");
$classSynopsisInfo = $doc->createElement($synopsisInfoName, "&Methods;");
$classSynopsisInfo->setAttribute("role", "comment");
$classSynopsis->appendChild($classSynopsisInfo);

Expand Down Expand Up @@ -3612,7 +3698,7 @@ public function getClassSynopsisElement(DOMDocument $doc, array $classMap, array

if (!empty($parentsWithInheritedMethods)) {
$classSynopsis->appendChild(new DOMText("\n\n "));
$classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedMethods;");
$classSynopsisInfo = $doc->createElement($synopsisInfoName, "&InheritedMethods;");
$classSynopsisInfo->setAttribute("role", "comment");
$classSynopsis->appendChild($classSynopsisInfo);

Expand Down Expand Up @@ -3650,8 +3736,7 @@ private static function createOoElement(
): ?DOMElement {
$indentation = str_repeat(" ", $indentationLevel);

if ($classInfo->type !== "class" && $classInfo->type !== "interface") {
echo "Class synopsis generation is not implemented for " . $classInfo->type . "\n";
if ($classInfo->type === "enum") {
return null;
}

Expand Down Expand Up @@ -3745,6 +3830,8 @@ private function collectInheritedMembers(
);
}

$isEnum = $this->type === "enum";

foreach ($this->implements as $parent) {
$parentInfo = $classMap[$parent->toString()] ?? null;
if (!$parentInfo) {
Expand All @@ -3758,13 +3845,25 @@ private function collectInheritedMembers(
$unusedParentsWithInheritedProperties = [];
$unusedParentsWithInheritedMethods = [];

$parentInfo->collectInheritedMembers(
$parentsWithInheritedConstants,
$unusedParentsWithInheritedProperties,
$unusedParentsWithInheritedMethods,
$hasConstructor,
$classMap
);
if ($isEnum) {
$parentInfo->collectInheritedMembers(
$parentsWithInheritedConstants,
$unusedParentsWithInheritedProperties,
// We only want to collect inherited methods in case of enums
$parentsWithInheritedMethods,
$hasConstructor,
$classMap
);
} else {
$parentInfo->collectInheritedMembers(
$parentsWithInheritedConstants,
$unusedParentsWithInheritedProperties,
// We only want to collect inherited methods in case of enums
$unusedParentsWithInheritedMethods,
$hasConstructor,
$classMap
);
}
}
}

Expand Down Expand Up @@ -3886,14 +3985,14 @@ public function __clone()
/**
* @param Name[] $parents
*/
private function appendInheritedMemberSectionToClassSynopsis(DOMDocument $doc, DOMElement $classSynopsis, array $parents, string $label, string $inheritedLabel): void
private function appendInheritedMemberSectionToClassSynopsis(DOMDocument $doc, DOMElement $classSynopsis, array $parents, string $synopsisInfoName, string $label, string $inheritedLabel): void
{
if (empty($parents)) {
return;
}

$classSynopsis->appendChild(new DOMText("\n\n "));
$classSynopsisInfo = $doc->createElement("classsynopsisinfo", "$inheritedLabel");
$classSynopsisInfo = $doc->createElement($synopsisInfoName, "$inheritedLabel");
$classSynopsisInfo->setAttribute("role", "comment");
$classSynopsis->appendChild($classSynopsisInfo);

Expand Down Expand Up @@ -4738,7 +4837,10 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
);
} else if ($classStmt instanceof Stmt\EnumCase) {
$enumCaseInfos[] = new EnumCaseInfo(
$classStmt->name->toString(), $classStmt->expr);
$classStmt->name->toString(),
$classStmt->expr,
$classStmt->expr ? $prettyPrinter->prettyPrintExpr($classStmt->expr) : null
);
} else {
throw new Exception("Not implemented {$classStmt->getType()}");
}
Expand Down
41 changes: 41 additions & 0 deletions classsynopses/random-intervalboundary.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<enumsynopsis>
<enumname>Random\IntervalBoundary</enumname>
<modifier role="enum_backing_type">int</modifier>

<oointerface>
<modifier>implements</modifier>
<interfacename>BackedEnum</interfacename>
</oointerface>

<synopsisinfo role="comment">&Constants;</synopsisinfo>
<fieldsynopsis>
<modifier>const</modifier>
<type>int</type>
<varname linkend="random-intervalboundary.constants.closedc">Random\IntervalBoundary::ClosedC</varname>
<initializer>2</initializer>
</fieldsynopsis>

<synopsisinfo role="comment">&EnumCases;</synopsisinfo>
<enumitem>
<enumidentifier>ClosedOpen</enumidentifier>
<enumvalue><initializer>1</initializer></enumvalue>
</enumitem>
<enumitem>
<enumidentifier>ClosedClosed</enumidentifier>
<enumvalue><initializer>2</initializer></enumvalue>
</enumitem>
<enumitem>
<enumidentifier>OpenClosed</enumidentifier>
<enumvalue><initializer>3</initializer></enumvalue>
</enumitem>
<enumitem>
<enumidentifier>OpenOpen</enumidentifier>
<enumvalue><initializer>4</initializer></enumvalue>
</enumitem>

<synopsisinfo role="comment">&InheritedMethods;</synopsisinfo>
<xi:include xpointer="xmlns(db=http://docbook.org/ns/docbook) xpointer(id('class.unitenum')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[@role='UnitEnum'])">
<xi:fallback/>
</xi:include>
</enumsynopsis>
11 changes: 6 additions & 5 deletions ext/random/random.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,12 @@ public function __serialize(): array {}
public function __unserialize(array $data): void {}
}

enum IntervalBoundary {
case ClosedOpen;
case ClosedClosed;
case OpenClosed;
case OpenOpen;
enum IntervalBoundary: int {
const int ClosedC = 2;
case ClosedOpen = 1;
case ClosedClosed = IntervalBoundary::ClosedC;
case OpenClosed = 3;
case OpenOpen = 4;
}

/**
Expand Down
Loading
Loading