@@ -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
642708class 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
677759class 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+
857990function 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 */
11811347function generateFunctionEntries (?Name $ className , array $ funcInfos ): string {
11821348 $ code = "" ;
0 commit comments