Skip to content

Commit 32d4821

Browse files
committed
Support generating internal enum decl from stubs
1 parent df32267 commit 32d4821

File tree

4 files changed

+192
-93
lines changed

4 files changed

+192
-93
lines changed

build/gen_stub.php

Lines changed: 140 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
use PhpParser\Node\Name;
99
use PhpParser\Node\Stmt;
1010
use PhpParser\Node\Stmt\Class_;
11+
use PhpParser\Node\Stmt\Enum_;
1112
use PhpParser\Node\Stmt\Interface_;
13+
use PhpParser\Node\Stmt\Trait_;
1214
use PhpParser\PrettyPrinter\Standard;
1315
use PhpParser\PrettyPrinterAbstract;
1416

@@ -1324,6 +1326,51 @@ public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDoc
13241326
}
13251327
}
13261328

1329+
function initializeZval(string $zvalName, $value): string
1330+
{
1331+
$code = "\tzval $zvalName;\n";
1332+
1333+
switch (gettype($value)) {
1334+
case "NULL":
1335+
$code .= "\tZVAL_NULL(&$zvalName);\n";
1336+
break;
1337+
1338+
case "boolean":
1339+
$code .= "\tZVAL_BOOL(&$zvalName, " . ((int) $value) . ");\n";
1340+
break;
1341+
1342+
case "integer":
1343+
$code .= "\tZVAL_LONG(&$zvalName, $value);\n";
1344+
break;
1345+
1346+
case "double":
1347+
$code .= "\tZVAL_DOUBLE(&$zvalName, $value);\n";
1348+
break;
1349+
1350+
case "string":
1351+
if ($value === "") {
1352+
$code .= "\tZVAL_EMPTY_STRING(&$zvalName);\n";
1353+
} else {
1354+
$code .= "\tzend_string *{$zvalName}_str = zend_string_init(\"$value\", sizeof(\"$value\") - 1, 1);\n";
1355+
$code .= "\tZVAL_STR(&$zvalName, {$zvalName}_str);\n";
1356+
}
1357+
break;
1358+
1359+
case "array":
1360+
if (empty($value)) {
1361+
$code .= "\tZVAL_EMPTY_ARRAY(&$zvalName);\n";
1362+
} else {
1363+
throw new Exception("Unimplemented default value");
1364+
}
1365+
break;
1366+
1367+
default:
1368+
throw new Exception("Invalid default value");
1369+
}
1370+
1371+
return $code;
1372+
}
1373+
13271374
class PropertyInfo
13281375
{
13291376
/** @var PropertyName */
@@ -1361,10 +1408,8 @@ public function getDeclaration(): string {
13611408
$defaultValueConstant = false;
13621409
if ($this->defaultValue === null) {
13631410
$defaultValue = null;
1364-
$defaultValueType = "undefined";
13651411
} else {
13661412
$defaultValue = $this->evaluateDefaultValue($defaultValueConstant);
1367-
$defaultValueType = gettype($defaultValue);
13681413
}
13691414

13701415
if ($defaultValueConstant) {
@@ -1411,80 +1456,26 @@ public function getDeclaration(): string {
14111456
}
14121457
}
14131458

1414-
$code .= $this->initializeValue($defaultValueType, $defaultValue, $this->type !== null);
1459+
$zvalName = "property_{$this->name->property}_default_value";
1460+
if ($this->defaultValue === null && $this->type !== null) {
1461+
$code .= "\tzval $zvalName;\n\tZVAL_UNDEF(&$zvalName);\n";
1462+
} else {
1463+
$code .= initializeZval($zvalName, $defaultValue);
1464+
}
14151465

14161466
$code .= "\tzend_string *property_{$propertyName}_name = zend_string_init(\"$propertyName\", sizeof(\"$propertyName\") - 1, 1);\n";
14171467
$nameCode = "property_{$propertyName}_name";
14181468

14191469
if ($this->type !== null) {
1420-
$code .= "\tzend_declare_typed_property(class_entry, $nameCode, &property_{$propertyName}_default_value, " . $this->getFlagsAsString() . ", NULL, $typeCode);\n";
1470+
$code .= "\tzend_declare_typed_property(class_entry, $nameCode, &$zvalName, " . $this->getFlagsAsString() . ", NULL, $typeCode);\n";
14211471
} else {
1422-
$code .= "\tzend_declare_property_ex(class_entry, $nameCode, &property_{$propertyName}_default_value, " . $this->getFlagsAsString() . ", NULL);\n";
1472+
$code .= "\tzend_declare_property_ex(class_entry, $nameCode, &$zvalName, " . $this->getFlagsAsString() . ", NULL);\n";
14231473
}
14241474
$code .= "\tzend_string_release(property_{$propertyName}_name);\n";
14251475

14261476
return $code;
14271477
}
14281478

1429-
/**
1430-
* @param mixed $value
1431-
*/
1432-
private function initializeValue(string $type, $value, bool $isTyped): string
1433-
{
1434-
$name = $this->name->property;
1435-
$zvalName = "property_{$name}_default_value";
1436-
1437-
$code = "\tzval $zvalName;\n";
1438-
1439-
switch ($type) {
1440-
case "undefined":
1441-
if ($isTyped) {
1442-
$code .= "\tZVAL_UNDEF(&$zvalName);\n";
1443-
} else {
1444-
$code .= "\tZVAL_NULL(&$zvalName);\n";
1445-
}
1446-
break;
1447-
1448-
case "NULL":
1449-
$code .= "\tZVAL_NULL(&$zvalName);\n";
1450-
break;
1451-
1452-
case "boolean":
1453-
$code .= "\tZVAL_BOOL(&$zvalName, " . ((int) $value) . ");\n";
1454-
break;
1455-
1456-
case "integer":
1457-
$code .= "\tZVAL_LONG(&$zvalName, $value);\n";
1458-
break;
1459-
1460-
case "double":
1461-
$code .= "\tZVAL_DOUBLE(&$zvalName, $value);\n";
1462-
break;
1463-
1464-
case "string":
1465-
if ($value === "") {
1466-
$code .= "\tZVAL_EMPTY_STRING(&$zvalName);\n";
1467-
} else {
1468-
$code .= "\tzend_string *{$zvalName}_str = zend_string_init(\"$value\", sizeof(\"$value\") - 1, 1);\n";
1469-
$code .= "\tZVAL_STR(&$zvalName, {$zvalName}_str);\n";
1470-
}
1471-
break;
1472-
1473-
case "array":
1474-
if (empty($value)) {
1475-
$code .= "\tZVAL_EMPTY_ARRAY(&$zvalName);\n";
1476-
} else {
1477-
throw new Exception("Unimplemented property default value");
1478-
}
1479-
break;
1480-
1481-
default:
1482-
throw new Exception("Invalid property default value");
1483-
}
1484-
1485-
return $code;
1486-
}
1487-
14881479
private function getFlagsAsString(): string
14891480
{
14901481
$flags = "ZEND_ACC_PUBLIC";
@@ -1568,6 +1559,33 @@ function (Expr $expr) use (&$defaultValueConstant) {
15681559
}
15691560
}
15701561

1562+
class EnumCaseInfo {
1563+
/** @var string */
1564+
public $name;
1565+
/** @var Expr|null */
1566+
public $value;
1567+
1568+
public function __construct(string $name, ?Expr $value) {
1569+
$this->name = $name;
1570+
$this->value = $value;
1571+
}
1572+
1573+
public function getDeclaration(): string {
1574+
$escapedName = addslashes($this->name);
1575+
if ($this->value === null) {
1576+
$code = "\n\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", NULL);\n";
1577+
} else {
1578+
$evaluator = new ConstExprEvaluator(function (Expr $expr) {
1579+
throw new Exception("Enum case $this->name has an unsupported value");
1580+
});
1581+
$zvalName = "enum_case_{$escapedName}_value";
1582+
$code = "\n" . initializeZval($zvalName, $evaluator->evaluateDirectly($this->value));
1583+
$code .= "\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", &$zvalName);\n";
1584+
}
1585+
return $code;
1586+
}
1587+
}
1588+
15711589
class ClassInfo {
15721590
/** @var Name */
15731591
public $name;
@@ -1577,6 +1595,8 @@ class ClassInfo {
15771595
public $type;
15781596
/** @var string|null */
15791597
public $alias;
1598+
/** @var SimpleType|null */
1599+
public $enumBackingType;
15801600
/** @var bool */
15811601
public $isDeprecated;
15821602
/** @var bool */
@@ -1591,37 +1611,44 @@ class ClassInfo {
15911611
public $propertyInfos;
15921612
/** @var FuncInfo[] */
15931613
public $funcInfos;
1614+
/** @var EnumCaseInfo[] */
1615+
public $enumCaseInfos;
15941616

15951617
/**
15961618
* @param Name[] $extends
15971619
* @param Name[] $implements
15981620
* @param PropertyInfo[] $propertyInfos
15991621
* @param FuncInfo[] $funcInfos
1622+
* @param EnumCaseInfo[] $enumCaseInfos
16001623
*/
16011624
public function __construct(
16021625
Name $name,
16031626
int $flags,
16041627
string $type,
16051628
?string $alias,
1629+
?SimpleType $enumBackingType,
16061630
bool $isDeprecated,
16071631
bool $isStrictProperties,
16081632
bool $isNotSerializable,
16091633
array $extends,
16101634
array $implements,
16111635
array $propertyInfos,
1612-
array $funcInfos
1636+
array $funcInfos,
1637+
array $enumCaseInfos
16131638
) {
16141639
$this->name = $name;
16151640
$this->flags = $flags;
16161641
$this->type = $type;
16171642
$this->alias = $alias;
1643+
$this->enumBackingType = $enumBackingType;
16181644
$this->isDeprecated = $isDeprecated;
16191645
$this->isStrictProperties = $isStrictProperties;
16201646
$this->isNotSerializable = $isNotSerializable;
16211647
$this->extends = $extends;
16221648
$this->implements = $implements;
16231649
$this->propertyInfos = $propertyInfos;
16241650
$this->funcInfos = $funcInfos;
1651+
$this->enumCaseInfos = $enumCaseInfos;
16251652
}
16261653

16271654
public function getRegistration(): string
@@ -1639,21 +1666,29 @@ public function getRegistration(): string
16391666
$code = "static zend_class_entry *register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n";
16401667

16411668
$code .= "{\n";
1642-
$code .= "\tzend_class_entry ce, *class_entry;\n\n";
1643-
if (count($this->name->parts) > 1) {
1644-
$className = $this->name->getLast();
1645-
$namespace = addslashes((string) $this->name->slice(0, -1));
1646-
1647-
$code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n";
1669+
if ($this->type == "enum") {
1670+
$name = addslashes((string) $this->name);
1671+
$backingType = $this->enumBackingType
1672+
? $this->enumBackingType->toTypeCode() : "IS_UNDEF";
1673+
$code .= "\tzend_class_entry *class_entry = zend_register_internal_enum(\"$name\", $backingType, class_{$escapedName}_methods);\n";
16481674
} else {
1649-
$code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n";
1650-
}
1675+
$code .= "\tzend_class_entry ce, *class_entry;\n\n";
1676+
if (count($this->name->parts) > 1) {
1677+
$className = $this->name->getLast();
1678+
$namespace = addslashes((string) $this->name->slice(0, -1));
16511679

1652-
if ($this->type === "class" || $this->type === "trait") {
1653-
$code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n";
1654-
} else {
1655-
$code .= "\tclass_entry = zend_register_internal_interface(&ce);\n";
1680+
$code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n";
1681+
} else {
1682+
$code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n";
1683+
}
1684+
1685+
if ($this->type === "class" || $this->type === "trait") {
1686+
$code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n";
1687+
} else {
1688+
$code .= "\tclass_entry = zend_register_internal_interface(&ce);\n";
1689+
}
16561690
}
1691+
16571692
if ($this->getFlagsAsString()) {
16581693
$code .= "\tclass_entry->ce_flags |= " . $this->getFlagsAsString() . ";\n";
16591694
}
@@ -1673,6 +1708,10 @@ function (Name $item) {
16731708
$code .= "\tzend_register_class_alias(\"" . str_replace("\\", "_", $this->alias) . "\", class_entry);\n";
16741709
}
16751710

1711+
foreach ($this->enumCaseInfos as $enumCase) {
1712+
$code .= $enumCase->getDeclaration();
1713+
}
1714+
16761715
foreach ($this->propertyInfos as $property) {
16771716
$code .= $property->getDeclaration();
16781717
}
@@ -2306,8 +2345,11 @@ function parseProperty(
23062345
/**
23072346
* @param PropertyInfo[] $properties
23082347
* @param FuncInfo[] $methods
2348+
* @param EnumCaseInfo[] $enumCases
23092349
*/
2310-
function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $methods): ClassInfo {
2350+
function parseClass(
2351+
Name $name, Stmt\ClassLike $class, array $properties, array $methods, array $enumCases
2352+
): ClassInfo {
23112353
$flags = $class instanceof Class_ ? $class->flags : 0;
23122354
$comment = $class->getDocComment();
23132355
$alias = null;
@@ -2334,26 +2376,38 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array
23342376
$implements = [];
23352377

23362378
if ($class instanceof Class_) {
2379+
$classKind = "class";
23372380
if ($class->extends) {
23382381
$extends[] = $class->extends;
23392382
}
23402383
$implements = $class->implements;
23412384
} elseif ($class instanceof Interface_) {
2385+
$classKind = "interface";
23422386
$extends = $class->extends;
2387+
} else if ($class instanceof Trait_) {
2388+
$classKind = "trait";
2389+
} else if ($class instanceof Enum_) {
2390+
$classKind = "enum";
2391+
$implements = $class->implements;
2392+
} else {
2393+
throw new Exception("Unknown class kind " . get_class($class));
23432394
}
23442395

23452396
return new ClassInfo(
23462397
$name,
23472398
$flags,
2348-
$class instanceof Class_ ? "class" : ($class instanceof Interface_ ? "interface" : "trait"),
2399+
$classKind,
23492400
$alias,
2401+
$class instanceof Enum_ && $class->scalarType !== null
2402+
? SimpleType::fromNode($class->scalarType) : null,
23502403
$isDeprecated,
23512404
$isStrictProperties,
23522405
$isNotSerializable,
23532406
$extends,
23542407
$implements,
23552408
$properties,
2356-
$methods
2409+
$methods,
2410+
$enumCases
23572411
);
23582412
}
23592413

@@ -2431,6 +2485,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
24312485
$className = $stmt->namespacedName;
24322486
$propertyInfos = [];
24332487
$methodInfos = [];
2488+
$enumCaseInfos = [];
24342489
foreach ($stmt->stmts as $classStmt) {
24352490
$cond = handlePreprocessorConditions($conds, $classStmt);
24362491
if ($classStmt instanceof Stmt\Nop) {
@@ -2466,12 +2521,16 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
24662521
$classStmt,
24672522
$cond
24682523
);
2524+
} else if ($classStmt instanceof Stmt\EnumCase) {
2525+
$enumCaseInfos[] = new EnumCaseInfo(
2526+
$classStmt->name->toString(), $classStmt->expr);
24692527
} else {
24702528
throw new Exception("Not implemented {$classStmt->getType()}");
24712529
}
24722530
}
24732531

2474-
$fileInfo->classInfos[] = parseClass($className, $stmt, $propertyInfos, $methodInfos);
2532+
$fileInfo->classInfos[] = parseClass(
2533+
$className, $stmt, $propertyInfos, $methodInfos, $enumCaseInfos);
24752534
continue;
24762535
}
24772536

0 commit comments

Comments
 (0)