From 0f137f3e4bf16f8de2bd5c113092e8cee0b4d072 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 16:48:57 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Use=20list.append=20instead?= =?UTF-8?q?=20of=20yield=20from=20in=20iter=5Fcalls=5Fin=5Ffunction=5Fbody?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: tachyon-beep <544926+tachyon-beep@users.noreply.github.com> --- .jules/bolt.md | 3 +++ src/wardline/scanner/ast_primitives.py | 32 +++++++++++++++----------- 2 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 .jules/bolt.md diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..278f0212 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-06-18 - AST Traversal Performance +**Learning:** For hot-path AST traversal, eager list-appending (`list.append()`) is consistently faster than `yield from` recursion. +**Action:** Use list-appending instead of `yield from` for AST traversal to improve performance. diff --git a/src/wardline/scanner/ast_primitives.py b/src/wardline/scanner/ast_primitives.py index 70f565b3..6a092e8b 100644 --- a/src/wardline/scanner/ast_primitives.py +++ b/src/wardline/scanner/ast_primitives.py @@ -105,38 +105,44 @@ def iter_calls_in_function_body( values, base classes, metaclass keywords) are still attributed to ``node``. """ - def walk_node(current: ast.AST) -> Iterator[ast.Call]: + # Optimization: Eager list appending is significantly faster than recursive 'yield from' + # in CPython due to reduced generator delegation overhead on deep AST paths. + result: list[ast.Call] = [] + + def walk_node(current: ast.AST) -> None: if isinstance(current, (ast.FunctionDef, ast.AsyncFunctionDef)): for decorator in current.decorator_list: - yield from walk_node(decorator) - yield from _walk_argument_defaults(current.args) + walk_node(decorator) + _walk_argument_defaults(current.args) return if isinstance(current, ast.ClassDef): for decorator in current.decorator_list: - yield from walk_node(decorator) + walk_node(decorator) for base in current.bases: - yield from walk_node(base) + walk_node(base) for keyword in current.keywords: - yield from walk_node(keyword.value) + walk_node(keyword.value) return if isinstance(current, ast.Lambda): - yield from _walk_argument_defaults(current.args) + _walk_argument_defaults(current.args) return if isinstance(current, ast.Call): - yield current + result.append(current) for child in ast.iter_child_nodes(current): - yield from walk_node(child) + walk_node(child) - def _walk_argument_defaults(args: ast.arguments) -> Iterator[ast.Call]: + def _walk_argument_defaults(args: ast.arguments) -> None: for default in args.defaults: - yield from walk_node(default) + walk_node(default) for kw_default in args.kw_defaults: if kw_default is None: continue - yield from walk_node(kw_default) + walk_node(kw_default) for stmt in node.body: - yield from walk_node(stmt) + walk_node(stmt) + + return iter(result) def resolve_self_method_fqn(