3030
3131final class Ast implements BuildMode
3232{
33- /** @var array<string, array{name: string, type: string}> */
34- public array $ stored = [];
3533 /** @var array<string, string> */
36- public array $ storedNames = [];
34+ public array $ storedByPath = [];
35+ /** @var array<string, array{type: string, extractor: array}> */
36+ public array $ stored = [];
3737 public int $ storedFlags = 0 ;
3838
3939 public ?string $ curKey = null ;
4040
4141 private array $ output = [];
4242 private array $ skipped = [];
43+ private array $ actions = [];
4344 private ?string $ needsParent = null ;
4445
4546 public function __construct (
@@ -63,12 +64,21 @@ public function finalize(string $refMapFile, string $refMapFileJson): void
6364 $ dbSchema .= self ::stringifySchema ($ constructor , $ params )."\n" ;
6465 }
6566 $ dbSchemaJSON = (new TL (null ))->toJson ($ dbSchema );
67+
68+ $ actions = [];
69+ foreach ($ this ->actions as $ action ) {
70+ if ($ action ['action ' ]['_ ' ] === 'callOp ' ) {
71+ $ action ['action ' ]['args ' ] = array_values ($ action ['action ' ]['args ' ]);
72+ }
73+ $ actions [] = $ action ;
74+ }
6675 $ value = [
6776 '_ ' => 'fileReferenceOrigins ' ,
6877 'db_schema ' => $ dbSchema ,
6978 'db_schema_json ' => json_encode ($ dbSchemaJSON , flags: JSON_THROW_ON_ERROR ),
70- 'ctxs ' => $ this ->output ,
79+ 'origins ' => $ this ->output ,
7180 'skipped ' => $ this ->skipped ,
81+ 'actions ' => $ actions ,
7282 ];
7383 Magic::start (false );
7484
@@ -78,8 +88,6 @@ public function finalize(string $refMapFile, string $refMapFileJson): void
7888 $ TL ->init ($ s );
7989 $ serialized = $ TL ->serializeObject (['type ' => 'FileReferenceOrigins ' ], $ value , '' );
8090 $ valueDe = $ TL ->deserialize ($ serialized , ['type ' => '' , 'connection ' => null , 'encrypted ' => true ]);
81- file_put_contents ('/tmp/a.json ' , json_encode ($ valueDe , flags: JSON_THROW_ON_ERROR ));
82- file_put_contents ('/tmp/b.json ' , json_encode ($ value , flags: JSON_THROW_ON_ERROR ));
8391 Assert::true ($ value == $ valueDe );
8492 file_put_contents ($ refMapFile , $ serialized );
8593 file_put_contents ($ refMapFileJson , json_encode ($ valueDe , flags: JSON_THROW_ON_ERROR ));
@@ -114,17 +122,17 @@ public function addNode(TLContext $ctx, ?array $action = null, ?string $why = nu
114122 $ constructor = $ action ['stored_constructor ' ];
115123 unset($ action ['stored_constructor ' ]);
116124
117- $ names = $ this ->storedNames ;
125+ $ stored = $ this ->stored ;
118126 $ flags = [];
119127 if ($ this ->storedFlags ) {
120- foreach ($ names as $ name => $ type ) {
128+ foreach ($ stored as $ name => [ ' type ' => $ type] ) {
121129 if (str_starts_with ($ type , 'flags. ' )) {
122130 $ flags [$ name ] = $ type ;
123131 }
124132 }
125- $ names = [
126- 'flags ' => ' # ' ,
127- ...$ names ,
133+ $ stored = [
134+ 'flags ' => [ ' type ' => ' # ' ] ,
135+ ...$ stored ,
128136 ];
129137 }
130138 $ skipped = [];
@@ -134,7 +142,7 @@ public function addNode(TLContext $ctx, ?array $action = null, ?string $why = nu
134142 foreach ($ existing as $ name => $ type ) {
135143 if (str_starts_with ($ type , 'flags. ' )) {
136144 if (isset ($ flags [$ name ])) {
137- unset($ flags [$ name ], $ names [$ name ]);
145+ unset($ flags [$ name ], $ stored [$ name ]);
138146 } else {
139147 $ skipped []= $ name ;
140148 }
@@ -144,29 +152,74 @@ public function addNode(TLContext $ctx, ?array $action = null, ?string $why = nu
144152 throw new AssertionError ("Have leftover flags: " .implode (' ' , $ flags ));
145153 }
146154 foreach ($ existing as $ name => $ type ) {
147- if (isset ($ names [$ name ])) {
148- if ($ names [$ name ] === $ type ) {
149- unset($ names [$ name ]);
155+ if (isset ($ stored [$ name ])) {
156+ if ($ stored [$ name][ ' type ' ] === $ type ) {
157+ unset($ stored [$ name ]);
150158 } else {
151- throw new AssertionError ("Type mismatch for $ constructor. $ name: have {$ names [$ name ]}, need $ type " );
159+ throw new AssertionError ("Type mismatch for $ constructor. $ name: have {$ stored [$ name][ ' type ' ]}, need $ type " );
152160 }
153161 } elseif (!str_starts_with ($ type , 'flags. ' ) && $ name !== 'flags ' ) {
154162 throw new AssertionError ("Missing pre-existing parameter $ constructor. $ name for $ constructor " );
155163 }
156164 }
157- foreach ($ names as $ name => $ type ) {
165+ foreach ($ stored as $ name => [ ' type ' => $ type] ) {
158166 throw new AssertionError ("Leftover parameter $ constructor. $ name: $ type for " .self ::stringifySchema ($ constructor , $ existing ));
159167 }
160168 } else {
161- $ this ->outputSchema [$ constructor ] = $ names ;
169+ $ types = [];
170+ foreach ($ stored as $ name => ['type ' => $ type ]) {
171+ $ types [$ name ] = $ type ;
172+ }
173+ $ this ->outputSchema [$ constructor ] = $ types ;
174+ }
175+
176+ if (isset ($ this ->actions [$ constructor ])) {
177+ $ existingAction = $ this ->actions [$ constructor ];
178+ Assert::eq ($ constructor , $ existingAction ['stored_constructor ' ]);
179+ $ existingAction = $ existingAction ['action ' ];
180+ Assert::eq ($ existingAction ['_ ' ], $ action ['_ ' ]);
181+
182+ // It's okay to fill missing params as the source of the data is always the same,
183+ // aka the source_constructor will always be of the same type, it should have all
184+ // needed flags, and the behavior will be consistent.
185+ if ($ action ['_ ' ] === 'getMessageOp ' ) {
186+ if (!isset ($ existingAction ['from_scheduled ' ]) && isset ($ action ['from_scheduled ' ])) {
187+ $ existingAction ['from_scheduled ' ] = $ action ['from_scheduled ' ];
188+ } elseif (isset ($ existingAction ['from_scheduled ' ]) && !isset ($ action ['from_scheduled ' ])) {
189+ $ action ['from_scheduled ' ] = $ existingAction ['from_scheduled ' ];
190+ }
191+ } else {
192+ Assert::eq ($ action ['_ ' ], 'callOp ' );
193+ foreach ($ action ['args ' ] as $ k => $ arg ) {
194+ Assert::string ($ k );
195+ if (!isset ($ existingAction ['args ' ][$ arg ['key ' ]])) {
196+ $ existingAction ['args ' ][$ arg ['key ' ]] = $ arg ;
197+ }
198+ }
199+ foreach ($ existingAction ['args ' ] as $ k => $ arg ) {
200+ Assert::string ($ k );
201+ if (!isset ($ action ['args ' ][$ arg ['key ' ]])) {
202+ $ action ['args ' ][$ arg ['key ' ]] = $ arg ;
203+ }
204+ }
205+ }
206+ Assert::eq ($ existingAction , $ action , "Mismatched actions for $ constructor " );
207+
208+ $ this ->actions [$ constructor ]['action ' ] = $ existingAction ;
209+ } else {
210+ $ this ->actions [$ constructor ] = [
211+ '_ ' => 'action ' ,
212+ 'stored_constructor ' => $ constructor ,
213+ 'action ' => $ action ,
214+ ];
162215 }
163216
164217 $ out = [
165218 '_ ' => 'origin ' ,
166219 'predicate ' => $ ctx ->position ,
167220 'is_constructor ' => $ ctx ->isConstructor ,
168- 'action ' => $ action ,
169221 'stored_constructor ' => $ constructor ,
222+ 'stored_params ' => array_column ($ this ->stored , 'extractor ' ),
170223 'skipped_flags ' => $ skipped ,
171224 'parent_is_constructor ' => false ,
172225 ];
@@ -178,7 +231,7 @@ public function addNode(TLContext $ctx, ?array $action = null, ?string $why = nu
178231
179232 $ this ->storedFlags = 0 ;
180233 $ this ->stored = [];
181- $ this ->storedNames = [];
234+ $ this ->storedByPath = [];
182235 Assert::null ($ why );
183236 } elseif ($ why !== null ) {
184237 $ this ->skipped [] = [
@@ -189,7 +242,7 @@ public function addNode(TLContext $ctx, ?array $action = null, ?string $why = nu
189242 ];
190243 Assert::null ($ action );
191244 Assert::isEmpty ($ this ->stored );
192- Assert::isEmpty ($ this ->storedNames );
245+ Assert::isEmpty ($ this ->storedByPath );
193246 Assert::eq ($ this ->storedFlags , 0 );
194247 } else {
195248 throw new AssertionError ("Either 'action' or 'why' must be provided. " );
0 commit comments