@@ -5691,6 +5691,58 @@ static void wd_push(wd_stack_t *s, TSNode node, const char *enclosing_qn) {
56915691 s -> data [s -> top ++ ] = (walk_defs_frame_t ){node , enclosing_qn };
56925692}
56935693
5694+ /* Push all children of `node` in REVERSE order (so they pop in source order)
5695+ * using a LINEAR traversal. Index-based ts_node_child(i) is O(i) per call in
5696+ * tree-sitter, so a reverse index loop is O(n^2) per node — a file whose
5697+ * root has ~580k flat siblings (ms-typescript's reallyLargeFile.ts fourslash
5698+ * fixture) needed ~1.7e11 child-iterator steps and hung extraction for
5699+ * hours; the supervisor then killed the silent worker as a hang and
5700+ * quarantined innocent files off the stale marker. Collect the children
5701+ * FORWARD with a TSTreeCursor (O(1) amortized per step) into a scratch
5702+ * buffer, then push in reverse. Small nodes keep the direct index loop —
5703+ * no cursor/buffer overhead on the overwhelmingly common case. */
5704+ enum { WD_CURSOR_MIN_CHILDREN = 64 };
5705+
5706+ /* Collect all `cc` children of `node` linearly via a TSTreeCursor into a
5707+ * malloc'd array (caller frees). Returns NULL for small nodes and on OOM —
5708+ * the caller then uses indexed ts_node_child access, which is fine (and
5709+ * cheaper) at small child counts and merely quadratic-but-correct on OOM. */
5710+ static TSNode * wd_collect_children (TSNode node , uint32_t cc ) {
5711+ if (cc < WD_CURSOR_MIN_CHILDREN ) {
5712+ return NULL ;
5713+ }
5714+ TSNode * buf = (TSNode * )malloc ((size_t )cc * sizeof (TSNode ));
5715+ if (!buf ) {
5716+ return NULL ;
5717+ }
5718+ TSTreeCursor cur = ts_tree_cursor_new (node );
5719+ uint32_t got = 0 ;
5720+ if (ts_tree_cursor_goto_first_child (& cur )) {
5721+ do {
5722+ buf [got ++ ] = ts_tree_cursor_current_node (& cur );
5723+ } while (got < cc && ts_tree_cursor_goto_next_sibling (& cur ));
5724+ }
5725+ ts_tree_cursor_delete (& cur );
5726+ if (got != cc ) {
5727+ /* Defensive: cursor and child_count disagree — fall back to indexed. */
5728+ free (buf );
5729+ return NULL ;
5730+ }
5731+ return buf ;
5732+ }
5733+
5734+ static void wd_push_children_reverse (wd_stack_t * s , TSNode node , const char * enclosing_qn ) {
5735+ uint32_t cc = ts_node_child_count (node );
5736+ if (cc == 0 ) {
5737+ return ;
5738+ }
5739+ TSNode * kids = wd_collect_children (node , cc );
5740+ for (int i = (int )cc - SKIP_CHAR ; i >= 0 ; i -- ) {
5741+ wd_push (s , kids ? kids [i ] : ts_node_child (node , (uint32_t )i ), enclosing_qn );
5742+ }
5743+ free (kids );
5744+ }
5745+
56945746// Push nested class nodes from a class body container onto the defs stack.
56955747// Iteratively walks into wrapper nodes (field_declaration, template_declaration).
56965748static void push_nested_class_nodes (TSNode body , const CBMLangSpec * spec , wd_stack_t * s ,
@@ -5702,8 +5754,10 @@ static void push_nested_class_nodes(TSNode body, const CBMLangSpec *spec, wd_sta
57025754 while (nc_stack .count > 0 ) {
57035755 TSNode cur = ts_nstack_pop (& nc_stack );
57045756 uint32_t nc = ts_node_child_count (cur );
5757+ /* Linear child access for wide class bodies (see wd_collect_children). */
5758+ TSNode * kids = wd_collect_children (cur , nc );
57055759 for (int i = (int )nc - SKIP_CHAR ; i >= 0 ; i -- ) {
5706- TSNode child = ts_node_child (cur , (uint32_t )i );
5760+ TSNode child = kids ? kids [ i ] : ts_node_child (cur , (uint32_t )i );
57075761 if (cbm_kind_in_set (child , spec -> class_node_types )) {
57085762 wd_push (s , child , enclosing_qn );
57095763 } else {
@@ -5714,6 +5768,7 @@ static void push_nested_class_nodes(TSNode body, const CBMLangSpec *spec, wd_sta
57145768 }
57155769 }
57165770 }
5771+ free (kids );
57175772 }
57185773}
57195774
@@ -6220,10 +6275,7 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
62206275 // resolve a null name and, for grammars where the kind has a `name`
62216276 // field, double-mint). Push children so nested tags/defs are still
62226277 // traversed, then skip the generic func path.
6223- uint32_t cc = ts_node_child_count (node );
6224- for (int i = (int )cc - SKIP_CHAR ; i >= 0 ; i -- ) {
6225- wd_push (& s , ts_node_child (node , (uint32_t )i ), frame .enclosing_class_qn );
6226- }
6278+ wd_push_children_reverse (& s , node , frame .enclosing_class_qn );
62276279 continue ;
62286280 }
62296281
@@ -6234,10 +6286,7 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
62346286 // generic extract_func_def below would double-mint a def whose name
62356287 // still carries the quotes. Push children so nested defines are still
62366288 // traversed, then skip the generic func path.
6237- uint32_t cc = ts_node_child_count (node );
6238- for (int i = (int )cc - SKIP_CHAR ; i >= 0 ; i -- ) {
6239- wd_push (& s , ts_node_child (node , (uint32_t )i ), frame .enclosing_class_qn );
6240- }
6289+ wd_push_children_reverse (& s , node , frame .enclosing_class_qn );
62416290 continue ;
62426291 }
62436292
@@ -6262,10 +6311,7 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
62626311 (strcmp (kind , "export_item" ) == 0 || strcmp (kind , "import_item" ) == 0 ) &&
62636312 ts_node_is_null (
62646313 find_first_descendant_by_kind (node , "func_type" , CBM_DESCENDANT_MAX_DEPTH ))) {
6265- uint32_t cc = ts_node_child_count (node );
6266- for (int i = (int )cc - SKIP_CHAR ; i >= 0 ; i -- ) {
6267- wd_push (& s , ts_node_child (node , (uint32_t )i ), frame .enclosing_class_qn );
6268- }
6314+ wd_push_children_reverse (& s , node , frame .enclosing_class_qn );
62696315 continue ;
62706316 }
62716317
@@ -6303,10 +6349,7 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
63036349 * the class/func paths on the namespace node itself. */
63046350 if (is_namespace_scope_kind (ctx -> language , kind )) {
63056351 const char * new_enclosing = compute_class_qn (ctx , node , frame .enclosing_class_qn );
6306- uint32_t nsc = ts_node_child_count (node );
6307- for (int i = (int )nsc - SKIP_CHAR ; i >= 0 ; i -- ) {
6308- wd_push (& s , ts_node_child (node , (uint32_t )i ), new_enclosing );
6309- }
6352+ wd_push_children_reverse (& s , node , new_enclosing );
63106353 continue ;
63116354 }
63126355
@@ -6317,10 +6360,11 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
63176360 continue ;
63186361 }
63196362
6320- uint32_t count = ts_node_child_count (node );
6321- for (int i = (int )count - SKIP_CHAR ; i >= 0 ; i -- ) {
6322- wd_push (& s , ts_node_child (node , (uint32_t )i ), frame .enclosing_class_qn );
6323- }
6363+ /* Default descent — THE hot loop: a file whose root has hundreds of
6364+ * thousands of flat siblings (580k comment lines in ms-typescript's
6365+ * reallyLargeFile.ts) lands here every visit, so linear child
6366+ * collection is mandatory (see wd_push_children_reverse). */
6367+ wd_push_children_reverse (& s , node , frame .enclosing_class_qn );
63246368 }
63256369 free (s .data );
63266370}
0 commit comments