@@ -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
615681class 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
650732class 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+
830963function 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 */
11541320function generateFunctionEntries (?string $ className , array $ funcInfos ): string {
11551321 $ code = "" ;
0 commit comments