Skip to content

Commit 24bd541

Browse files
committed
Add support for declaring properties in stubs
1 parent 22ab18c commit 24bd541

29 files changed

+318
-42
lines changed

Zend/zend_exceptions.stub.php

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ public function getTraceAsString(): string;
2222

2323
class Exception implements Throwable
2424
{
25+
/** @known */
26+
protected $message = "";
27+
private $string = "";
28+
protected $code = 0;
29+
protected $file = null;
30+
protected $line = null;
31+
2532
final private function __clone() {}
2633

2734
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null) {}
@@ -48,13 +55,29 @@ public function __toString(): string {}
4855

4956
class ErrorException extends Exception
5057
{
51-
public function __construct(string $message = "", int $code = 0, int $severity = E_ERROR, ?string $filename = null, ?int $line = null, ?Throwable $previous = null) {}
58+
protected $severity = E_ERROR;
59+
60+
public function __construct(
61+
string $message = "",
62+
int $code = 0,
63+
int $severity = E_ERROR,
64+
?string $filename = null,
65+
?int $line = null,
66+
?Throwable $previous = null
67+
) {}
5268

5369
final public function getSeverity(): int {}
5470
}
5571

5672
class Error implements Throwable
5773
{
74+
/** @known */
75+
protected $message = "";
76+
private $string = "";
77+
protected $code = 0;
78+
protected $file = null;
79+
protected $line = null;
80+
5881
/** @implementation-alias Exception::__clone */
5982
final private function __clone() {}
6083

Zend/zend_exceptions_arginfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: bc49b326136997660887b12f0c59f8a57b17ecaf */
2+
* Stub hash: edf97c65eba70b0d93d072f7fd94f2c0f31bff1b */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Throwable_getMessage, 0, 0, IS_STRING, 0)
55
ZEND_END_ARG_INFO()

build/gen_stub.php

Lines changed: 191 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ function processStubFile(string $stubFile, Context $context) {
3232
}
3333

3434
$arginfoFile = str_replace('.stub.php', '_arginfo.h', $stubFile);
35-
$legacyFile = str_replace('.stub.php', '_legacy_arginfo.h', $stubFile);
35+
$legacyArginfoFile = str_replace('.stub.php', '_legacy_arginfo.h', $stubFile);
36+
37+
$propertyDeclarationsFile = str_replace('.stub.php', '_properties.h', $stubFile);
38+
$legacyPropertyDeclarationsFile = str_replace('.stub.php', '_legacy_properties.h', $stubFile);
3639

3740
$stubCode = file_get_contents($stubFile);
3841
$stubHash = computeStubHash($stubCode);
@@ -54,8 +57,26 @@ function processStubFile(string $stubFile, Context $context) {
5457
$funcInfo->discardInfoForOldPhpVersions();
5558
}
5659
$arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
57-
if (file_put_contents($legacyFile, $arginfoCode)) {
58-
echo "Saved $legacyFile\n";
60+
if (file_put_contents($legacyArginfoFile, $arginfoCode)) {
61+
echo "Saved $legacyArginfoFile\n";
62+
}
63+
}
64+
65+
if ($fileInfo->generatePropertyDeclarations) {
66+
$propertyDeclarationsCode = generatePropertyDeclarationsCode($fileInfo, $stubHash);
67+
if (file_put_contents($propertyDeclarationsFile, $propertyDeclarationsCode)) {
68+
echo "Saved $propertyDeclarationsFile\n";
69+
}
70+
}
71+
72+
if ($fileInfo->generateLegacyPropertyDeclarations) {
73+
foreach ($fileInfo->getAllPropertyInfos() as $propertyInfo) {
74+
$propertyInfo->discardInfoForOldPhpVersions();
75+
}
76+
77+
$propertyDeclarationsCode = generateArgInfoCode($fileInfo, $stubHash);
78+
if (file_put_contents($legacyPropertyDeclarationsFile, $propertyDeclarationsCode)) {
79+
echo "Saved $legacyPropertyDeclarationsFile\n";
5980
}
6081
}
6182

@@ -453,7 +474,7 @@ public function equals(ReturnInfo $other): bool {
453474
}
454475
}
455476

456-
class FuncInfo {
477+
class FuncInfo extends MemberInfo {
457478
/** @var FunctionOrMethodName */
458479
public $name;
459480
/** @var int */
@@ -484,8 +505,8 @@ public function __construct(
484505
int $numRequiredArgs,
485506
?string $cond
486507
) {
508+
parent::__construct($flags);
487509
$this->name = $name;
488-
$this->flags = $flags;
489510
$this->aliasType = $aliasType;
490511
$this->alias = $alias;
491512
$this->isDeprecated = $isDeprecated;
@@ -602,7 +623,60 @@ public function getFunctionEntry(): string {
602623
}
603624
}
604625

605-
private function getFlagsAsString(): string
626+
public function discardInfoForOldPhpVersions(): void {
627+
$this->return->type = null;
628+
foreach ($this->args as $arg) {
629+
$arg->type = null;
630+
$arg->defaultValue = null;
631+
}
632+
}
633+
}
634+
635+
class PropertyInfo extends MemberInfo
636+
{
637+
/** @var string */
638+
public $name;
639+
/** @var bool */
640+
public $isKnownName;
641+
/** @var bool */
642+
public $isDeprecated;
643+
/** @var Type|null */
644+
public $type;
645+
/** @var string|null */
646+
public $value;
647+
648+
public function __construct(string $name, int $flags, bool $isKnownName, bool $isDeprecated, ?Type $type, ?string $value)
649+
{
650+
parent::__construct($flags);
651+
$this->name = $name;
652+
$this->isKnownName = $isKnownName;
653+
$this->isDeprecated = $isDeprecated;
654+
$this->type = $type;
655+
$this->value = $value;
656+
}
657+
658+
public function discardInfoForOldPhpVersions(): void {
659+
$this->type = null;
660+
}
661+
662+
public function getDeclaration(): string {
663+
$code = "";
664+
$code .= " zend_declare_property_null(ce, \"$this->name\", sizeof(\"$this->name\") - 1, " . $this->getFlagsAsString() . ")\n";
665+
666+
return $code;
667+
}
668+
}
669+
670+
abstract class MemberInfo {
671+
/** @var int */
672+
public $flags;
673+
674+
public function __construct(int $flags)
675+
{
676+
$this->flags = $flags;
677+
}
678+
679+
protected function getFlagsAsString(): string
606680
{
607681
$flags = "ZEND_ACC_PUBLIC";
608682
if ($this->flags & Class_::MODIFIER_PROTECTED) {
@@ -629,25 +703,20 @@ private function getFlagsAsString(): string
629703

630704
return $flags;
631705
}
632-
633-
public function discardInfoForOldPhpVersions(): void {
634-
$this->return->type = null;
635-
foreach ($this->args as $arg) {
636-
$arg->type = null;
637-
$arg->defaultValue = null;
638-
}
639-
}
640706
}
641707

642708
class ClassInfo {
643709
/** @var Name */
644710
public $name;
645711
/** @var FuncInfo[] */
646712
public $funcInfos;
713+
/** @var PropertyInfo[] */
714+
public $propertyInfos;
647715

648-
public function __construct(Name $name, array $funcInfos) {
716+
public function __construct(Name $name, array $funcInfos, array $propertyInfos) {
649717
$this->name = $name;
650718
$this->funcInfos = $funcInfos;
719+
$this->propertyInfos = $propertyInfos;
651720
}
652721
}
653722

@@ -662,6 +731,10 @@ class FileInfo {
662731
public $declarationPrefix = "";
663732
/** @var bool */
664733
public $generateLegacyArginfo = false;
734+
/** @var bool */
735+
public $generatePropertyDeclarations = false;
736+
/** @var bool */
737+
public $generateLegacyPropertyDeclarations = false;
665738

666739
/**
667740
* @return iterable<FuncInfo>
@@ -672,6 +745,15 @@ public function getAllFuncInfos(): iterable {
672745
yield from $classInfo->funcInfos;
673746
}
674747
}
748+
749+
/**
750+
* @return iterable<PropertyInfo>
751+
*/
752+
public function getAllPropertyInfos(): iterable {
753+
foreach ($this->classInfos as $classInfo) {
754+
yield from $classInfo->propertyInfos;
755+
}
756+
}
675757
}
676758

677759
class DocCommentTag {
@@ -854,6 +936,57 @@ function parseFunctionLike(
854936
);
855937
}
856938

939+
function parseProperty(
940+
Name $class,
941+
PrettyPrinterAbstract $prettyPrinter,
942+
int $flags,
943+
Stmt\PropertyProperty $property,
944+
?Node $type
945+
): PropertyInfo {
946+
$comment = $property->getDocComment();
947+
$isDeprecated = false;
948+
$isKnownName = false;
949+
$docType = false;
950+
951+
if ($comment) {
952+
$tags = parseDocComment($comment);
953+
foreach ($tags as $tag) {
954+
if ($tag->name === 'deprecated') {
955+
$isDeprecated = true;
956+
} else if ($tag->name === 'known') {
957+
$isKnownName = true;
958+
} else if ($tag->name === 'var') {
959+
$docType = true;
960+
}
961+
}
962+
}
963+
964+
$propertyType = $type ? Type::fromNode($type) : null;
965+
if ($propertyType === null && !$docType) {
966+
//throw new Exception("Missing type for property $class::\$$property->name");
967+
}
968+
969+
if ($property->default instanceof Expr\ConstFetch &&
970+
$property->default->name->toLowerString() === "null" &&
971+
$propertyType && !$propertyType->isNullable()
972+
) {
973+
$simpleType = $propertyType->tryToSimpleType();
974+
if ($simpleType === null) {
975+
throw new Exception(
976+
"Property $class::\$$property->name has null default, but is not nullable");
977+
}
978+
}
979+
980+
return new PropertyInfo(
981+
$property->name->__toString(),
982+
$flags,
983+
$isKnownName,
984+
$isDeprecated,
985+
$propertyType,
986+
$property->default ? $prettyPrinter->prettyPrintExpr($property->default) : null
987+
);
988+
}
989+
857990
function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string {
858991
foreach ($stmt->getComments() as $comment) {
859992
$text = trim($comment->getText());
@@ -926,13 +1059,14 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
9261059
if ($stmt instanceof Stmt\ClassLike) {
9271060
$className = $stmt->namespacedName;
9281061
$methodInfos = [];
1062+
$propertyInfos = [];
9291063
foreach ($stmt->stmts as $classStmt) {
9301064
$cond = handlePreprocessorConditions($conds, $classStmt);
9311065
if ($classStmt instanceof Stmt\Nop) {
9321066
continue;
9331067
}
9341068

935-
if (!$classStmt instanceof Stmt\ClassMethod) {
1069+
if (!$classStmt instanceof Stmt\ClassMethod && !$classStmt instanceof Stmt\Property) {
9361070
throw new Exception("Not implemented {$classStmt->getType()}");
9371071
}
9381072

@@ -942,19 +1076,32 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
9421076
}
9431077

9441078
if (!($flags & Class_::VISIBILITY_MODIFIER_MASK)) {
945-
throw new Exception("Method visibility modifier is required");
1079+
throw new Exception("Visibility modifier is required");
9461080
}
9471081

948-
$methodInfos[] = parseFunctionLike(
949-
$prettyPrinter,
950-
new MethodName($className, $classStmt->name->toString()),
951-
$flags,
952-
$classStmt,
953-
$cond
954-
);
1082+
if ($classStmt instanceof Stmt\Property) {
1083+
foreach ($classStmt->props as $property) {
1084+
$propertyInfos[] = parseProperty(
1085+
$className,
1086+
$prettyPrinter,
1087+
$flags,
1088+
$property,
1089+
$classStmt->type
1090+
);
1091+
}
1092+
1093+
} else if ($classStmt instanceof Stmt\ClassMethod) {
1094+
$methodInfos[] = parseFunctionLike(
1095+
$prettyPrinter,
1096+
new MethodName($className, $classStmt->name->toString()),
1097+
$flags,
1098+
$classStmt,
1099+
$cond
1100+
);
1101+
}
9551102
}
9561103

957-
$fileInfo->classInfos[] = new ClassInfo($className, $methodInfos);
1104+
$fileInfo->classInfos[] = new ClassInfo($className, $methodInfos, $propertyInfos);
9581105
continue;
9591106
}
9601107

@@ -986,6 +1133,10 @@ protected function pName_FullyQualified(Name\FullyQualified $node) {
9861133
$fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : "";
9871134
} else if ($tag->name === 'generate-legacy-arginfo') {
9881135
$fileInfo->generateLegacyArginfo = true;
1136+
} else if ($tag->name === 'generate-property-declarations') {
1137+
$fileInfo->generatePropertyDeclarations = true;
1138+
} else if ($tag->name === 'generate-legacy-property-declarations') {
1139+
$fileInfo->generateLegacyPropertyDeclarations = true;
9891140
}
9901141
}
9911142
}
@@ -1177,6 +1328,21 @@ function (FuncInfo $funcInfo) use($fileInfo, &$generatedFunctionDeclarations) {
11771328
return $code;
11781329
}
11791330

1331+
function generatePropertyDeclarationsCode(FileInfo $fileInfo, string $stubHash): string {
1332+
$code = "/* This is a generated file, edit the .stub.php file instead.\n"
1333+
. " * Stub hash: $stubHash */\n\n";
1334+
1335+
foreach ($fileInfo->classInfos as $class) {
1336+
$code .= "void declare_class_{$class->name}_properties(zend_class_entry *ce) {\n";
1337+
foreach ($class->propertyInfos as $property) {
1338+
$code .= $property->getDeclaration();
1339+
}
1340+
$code .= "}\n";
1341+
}
1342+
1343+
return $code;
1344+
}
1345+
11801346
/** @param FuncInfo[] $funcInfos */
11811347
function generateFunctionEntries(?Name $className, array $funcInfos): string {
11821348
$code = "";

0 commit comments

Comments
 (0)