Skip to content

Commit d7435a2

Browse files
committed
Add support for declaring properties in stubs
1 parent 1c411ed commit d7435a2

28 files changed

+312
-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

@@ -431,7 +452,7 @@ public function equals(ReturnInfo $other): bool {
431452
}
432453
}
433454

434-
class FuncInfo {
455+
class FuncInfo extends MemberInfo {
435456
/** @var FunctionOrMethodName */
436457
public $name;
437458
/** @var int */
@@ -462,8 +483,8 @@ public function __construct(
462483
int $numRequiredArgs,
463484
?string $cond
464485
) {
486+
parent::__construct($flags);
465487
$this->name = $name;
466-
$this->flags = $flags;
467488
$this->aliasType = $aliasType;
468489
$this->alias = $alias;
469490
$this->isDeprecated = $isDeprecated;
@@ -575,7 +596,60 @@ public function getFunctionEntry(): string {
575596
}
576597
}
577598

578-
private function getFlagsAsString(): string
599+
public function discardInfoForOldPhpVersions(): void {
600+
$this->return->type = null;
601+
foreach ($this->args as $arg) {
602+
$arg->type = null;
603+
$arg->defaultValue = null;
604+
}
605+
}
606+
}
607+
608+
class PropertyInfo extends MemberInfo
609+
{
610+
/** @var string */
611+
public $name;
612+
/** @var bool */
613+
public $isKnownName;
614+
/** @var bool */
615+
public $isDeprecated;
616+
/** @var Type|null */
617+
public $type;
618+
/** @var string|null */
619+
public $value;
620+
621+
public function __construct(string $name, int $flags, bool $isKnownName, bool $isDeprecated, ?Type $type, ?string $value)
622+
{
623+
parent::__construct($flags);
624+
$this->name = $name;
625+
$this->isKnownName = $isKnownName;
626+
$this->isDeprecated = $isDeprecated;
627+
$this->type = $type;
628+
$this->value = $value;
629+
}
630+
631+
public function discardInfoForOldPhpVersions(): void {
632+
$this->type = null;
633+
}
634+
635+
public function getDeclaration(): string {
636+
$code = "";
637+
$code .= " zend_declare_property_null(ce, \"$this->name\", sizeof(\"$this->name\") - 1, " . $this->getFlagsAsString() . ")\n";
638+
639+
return $code;
640+
}
641+
}
642+
643+
abstract class MemberInfo {
644+
/** @var int */
645+
public $flags;
646+
647+
public function __construct(int $flags)
648+
{
649+
$this->flags = $flags;
650+
}
651+
652+
protected function getFlagsAsString(): string
579653
{
580654
$flags = "ZEND_ACC_PUBLIC";
581655
if ($this->flags & Class_::MODIFIER_PROTECTED) {
@@ -602,25 +676,20 @@ private function getFlagsAsString(): string
602676

603677
return $flags;
604678
}
605-
606-
public function discardInfoForOldPhpVersions(): void {
607-
$this->return->type = null;
608-
foreach ($this->args as $arg) {
609-
$arg->type = null;
610-
$arg->defaultValue = null;
611-
}
612-
}
613679
}
614680

615681
class ClassInfo {
616682
/** @var string */
617683
public $name;
618684
/** @var FuncInfo[] */
619685
public $funcInfos;
686+
/** @var PropertyInfo[] */
687+
public $propertyInfos;
620688

621-
public function __construct(string $name, array $funcInfos) {
689+
public function __construct(string $name, array $funcInfos, array $propertyInfos) {
622690
$this->name = $name;
623691
$this->funcInfos = $funcInfos;
692+
$this->propertyInfos = $propertyInfos;
624693
}
625694
}
626695

@@ -635,6 +704,10 @@ class FileInfo {
635704
public $declarationPrefix = "";
636705
/** @var bool */
637706
public $generateLegacyArginfo = false;
707+
/** @var bool */
708+
public $generatePropertyDeclarations = false;
709+
/** @var bool */
710+
public $generateLegacyPropertyDeclarations = false;
638711

639712
/**
640713
* @return iterable<FuncInfo>
@@ -645,6 +718,15 @@ public function getAllFuncInfos(): iterable {
645718
yield from $classInfo->funcInfos;
646719
}
647720
}
721+
722+
/**
723+
* @return iterable<PropertyInfo>
724+
*/
725+
public function getAllPropertyInfos(): iterable {
726+
foreach ($this->classInfos as $classInfo) {
727+
yield from $classInfo->propertyInfos;
728+
}
729+
}
648730
}
649731

650732
class DocCommentTag {
@@ -827,6 +909,57 @@ function parseFunctionLike(
827909
);
828910
}
829911

912+
function parseProperty(
913+
string $class,
914+
PrettyPrinterAbstract $prettyPrinter,
915+
int $flags,
916+
Stmt\PropertyProperty $property,
917+
?Node $type
918+
): PropertyInfo {
919+
$comment = $property->getDocComment();
920+
$isDeprecated = false;
921+
$isKnownName = false;
922+
$docType = false;
923+
924+
if ($comment) {
925+
$tags = parseDocComment($comment);
926+
foreach ($tags as $tag) {
927+
if ($tag->name === 'deprecated') {
928+
$isDeprecated = true;
929+
} else if ($tag->name === 'known') {
930+
$isKnownName = true;
931+
} else if ($tag->name === 'var') {
932+
$docType = true;
933+
}
934+
}
935+
}
936+
937+
$propertyType = $type ? Type::fromNode($type) : null;
938+
if ($propertyType === null && !$docType) {
939+
//throw new Exception("Missing type for property $class::\$$property->name");
940+
}
941+
942+
if ($property->default instanceof Expr\ConstFetch &&
943+
$property->default->name->toLowerString() === "null" &&
944+
$propertyType && !$propertyType->isNullable()
945+
) {
946+
$simpleType = $propertyType->tryToSimpleType();
947+
if ($simpleType === null) {
948+
throw new Exception(
949+
"Property $class::\$$property->name has null default, but is not nullable");
950+
}
951+
}
952+
953+
return new PropertyInfo(
954+
$property->name->__toString(),
955+
$flags,
956+
$isKnownName,
957+
$isDeprecated,
958+
$propertyType,
959+
$property->default ? $prettyPrinter->prettyPrintExpr($property->default) : null
960+
);
961+
}
962+
830963
function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string {
831964
foreach ($stmt->getComments() as $comment) {
832965
$text = trim($comment->getText());
@@ -899,13 +1032,14 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
8991032
if ($stmt instanceof Stmt\ClassLike) {
9001033
$className = $stmt->name->toString();
9011034
$methodInfos = [];
1035+
$propertyInfos = [];
9021036
foreach ($stmt->stmts as $classStmt) {
9031037
$cond = handlePreprocessorConditions($conds, $classStmt);
9041038
if ($classStmt instanceof Stmt\Nop) {
9051039
continue;
9061040
}
9071041

908-
if (!$classStmt instanceof Stmt\ClassMethod) {
1042+
if (!$classStmt instanceof Stmt\ClassMethod && !$classStmt instanceof Stmt\Property) {
9091043
throw new Exception("Not implemented {$classStmt->getType()}");
9101044
}
9111045

@@ -915,19 +1049,32 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
9151049
}
9161050

9171051
if (!($flags & Class_::VISIBILITY_MODIFIER_MASK)) {
918-
throw new Exception("Method visibility modifier is required");
1052+
throw new Exception("Visibility modifier is required");
9191053
}
9201054

921-
$methodInfos[] = parseFunctionLike(
922-
$prettyPrinter,
923-
new MethodName($className, $classStmt->name->toString()),
924-
$flags,
925-
$classStmt,
926-
$cond
927-
);
1055+
if ($classStmt instanceof Stmt\Property) {
1056+
foreach ($classStmt->props as $property) {
1057+
$propertyInfos[] = parseProperty(
1058+
$className,
1059+
$prettyPrinter,
1060+
$flags,
1061+
$property,
1062+
$classStmt->type
1063+
);
1064+
}
1065+
1066+
} else if ($classStmt instanceof Stmt\ClassMethod) {
1067+
$methodInfos[] = parseFunctionLike(
1068+
$prettyPrinter,
1069+
new MethodName($className, $classStmt->name->toString()),
1070+
$flags,
1071+
$classStmt,
1072+
$cond
1073+
);
1074+
}
9281075
}
9291076

930-
$fileInfo->classInfos[] = new ClassInfo($className, $methodInfos);
1077+
$fileInfo->classInfos[] = new ClassInfo($className, $methodInfos, $propertyInfos);
9311078
continue;
9321079
}
9331080

@@ -959,6 +1106,10 @@ protected function pName_FullyQualified(Name\FullyQualified $node) {
9591106
$fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : "";
9601107
} else if ($tag->name === 'generate-legacy-arginfo') {
9611108
$fileInfo->generateLegacyArginfo = true;
1109+
} else if ($tag->name === 'generate-property-declarations') {
1110+
$fileInfo->generatePropertyDeclarations = true;
1111+
} else if ($tag->name === 'generate-legacy-property-declarations') {
1112+
$fileInfo->generateLegacyPropertyDeclarations = true;
9621113
}
9631114
}
9641115
}
@@ -1150,6 +1301,21 @@ function (FuncInfo $funcInfo) use($fileInfo, &$generatedFunctionDeclarations) {
11501301
return $code;
11511302
}
11521303

1304+
function generatePropertyDeclarationsCode(FileInfo $fileInfo, string $stubHash): string {
1305+
$code = "/* This is a generated file, edit the .stub.php file instead.\n"
1306+
. " * Stub hash: $stubHash */\n\n";
1307+
1308+
foreach ($fileInfo->classInfos as $class) {
1309+
$code .= "void declare_class_{$class->name}_properties(zend_class_entry *ce) {\n";
1310+
foreach ($class->propertyInfos as $property) {
1311+
$code .= $property->getDeclaration();
1312+
}
1313+
$code .= "}\n";
1314+
}
1315+
1316+
return $code;
1317+
}
1318+
11531319
/** @param FuncInfo[] $funcInfos */
11541320
function generateFunctionEntries(?string $className, array $funcInfos): string {
11551321
$code = "";

0 commit comments

Comments
 (0)