@@ -143,7 +143,7 @@ public function get_child_nodes( ?string $rule_name = null ): array {
143143 if ( $ this ->was_mutated () ) {
144144 return parent ::get_child_nodes ( $ rule_name );
145145 }
146- return $ this ->intern_all ( wp_sqlite_mysql_native_ast_get_child_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
146+ return $ this ->intern_nodes ( wp_sqlite_mysql_native_ast_get_child_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
147147 }
148148
149149 /** @inheritDoc */
@@ -167,7 +167,7 @@ public function get_descendant_nodes( ?string $rule_name = null ): array {
167167 if ( $ this ->was_mutated () ) {
168168 return parent ::get_descendant_nodes ( $ rule_name );
169169 }
170- return $ this ->intern_all ( wp_sqlite_mysql_native_ast_get_descendant_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
170+ return $ this ->intern_nodes ( wp_sqlite_mysql_native_ast_get_descendant_nodes ( $ this ->native_ast , $ this ->native_node_index , $ rule_name ) );
171171 }
172172
173173 /** @inheritDoc */
@@ -231,12 +231,62 @@ private function intern( $value ) {
231231 /**
232232 * Intern every entry in an accessor return array.
233233 *
234+ * Hot path: this runs once per descendant when a caller walks the tree,
235+ * so cache lookup and the cache-miss write are inlined and the cache
236+ * reference is hoisted out of the loop.
237+ *
234238 * @param array $values
235239 * @return array
236240 */
237241 private function intern_all ( array $ values ): array {
242+ if ( ! $ values ) {
243+ return $ values ;
244+ }
245+ $ cache = $ this ->cache ?? $ this ->ensure_cache ();
246+ $ nodes = &$ cache ->nodes ;
247+ foreach ( $ values as $ i => $ value ) {
248+ if ( ! $ value instanceof WP_MySQL_Native_Parser_Node ) {
249+ continue ;
250+ }
251+ $ index = $ value ->native_node_index ;
252+ if ( null === $ index ) {
253+ continue ;
254+ }
255+ if ( isset ( $ nodes [ $ index ] ) ) {
256+ $ values [ $ i ] = $ nodes [ $ index ];
257+ } else {
258+ $ value ->cache = $ cache ;
259+ $ nodes [ $ index ] = $ value ;
260+ }
261+ }
262+ return $ values ;
263+ }
264+
265+ /**
266+ * Intern array of guaranteed-node results (no token/null mixing).
267+ *
268+ * Used by `get_child_nodes()` / `get_descendant_nodes()` whose Rust
269+ * bridge returns only WP_MySQL_Native_Parser_Node instances. Skips
270+ * the per-item `instanceof` check that intern_all() must do for the
271+ * mixed `get_children()` / `get_descendants()` arrays.
272+ *
273+ * @param array $values
274+ * @return array
275+ */
276+ private function intern_nodes ( array $ values ): array {
277+ if ( ! $ values ) {
278+ return $ values ;
279+ }
280+ $ cache = $ this ->cache ?? $ this ->ensure_cache ();
281+ $ nodes = &$ cache ->nodes ;
238282 foreach ( $ values as $ i => $ value ) {
239- $ values [ $ i ] = $ this ->intern ( $ value );
283+ $ index = $ value ->native_node_index ;
284+ if ( isset ( $ nodes [ $ index ] ) ) {
285+ $ values [ $ i ] = $ nodes [ $ index ];
286+ } else {
287+ $ value ->cache = $ cache ;
288+ $ nodes [ $ index ] = $ value ;
289+ }
240290 }
241291 return $ values ;
242292 }
0 commit comments