Skip to content

Materialize the matches before sbud describes them - #3605

Open
ESultanik wants to merge 1 commit into
masterfrom
fix/3584-sbud-materializes-matches
Open

ESultanik wants to merge 1 commit into
masterfrom
fix/3584-sbud-materializes-matches

Conversation

@ESultanik

Copy link
Copy Markdown
Collaborator

Closes #3584.

Analyzer.matches yields a top-level match as soon as it is found and attaches that match's children as the analysis continues. sbud bound the generator and drained it inside the comprehension that builds struc, so to_obj ran on each top-level match before its children existed.

one-member ZIP, sbud()                    1 node
one-member ZIP, sbud(matches=list(...))  55 nodes

The object is correctly shaped and almost entirely empty. Nothing in the signature or the docstring warned about it, which is the part #3577 is about: the method returns successfully with the wrong contents. The command line was unaffected, because __main__ materializes the matches before calling this.

The change

matches = list(self.matches() if matches is None else matches).

A supplied iterable is materialized too, not only the default. A caller passing a generator cannot know how far sbud needs it drained, and before this it got the same truncated tree — the issue does not mention that shape, but it was broken in exactly the same way:

sbud()                        55 nodes
sbud(matches=materialized)    55 nodes
sbud(matches=<generator>)     55 nodes
all three dicts equal: True

The docstring now says matches is an optimization for a caller that already has them, never a precondition.

Tests

tests/test_analyzer.py asserts whole-object equality across all three call shapes, which is the property the issue asks for and the version that cannot rot as the tree changes. Two guards around it:

  • test_the_tree_is_not_trivially_small, because whole-object equality passes happily if every caller gets the same truncated tree.
  • test_sbud_can_be_called_twice, since Analyzer.matches caches into matches_so_far and a second call has to agree with the first. It did not before this change.

All three fail with the change reverted. Full suite: 387 passed, 1,511 subtests. Blocking flake8: 0.

🤖 Generated with Claude Code

`Analyzer.matches` yields a top-level match as soon as it is found and
attaches that match's children as the analysis continues. `sbud` bound the
generator and drained it inside the comprehension that builds `struc`, so
`to_obj` ran on each top-level match before its children existed.

The result was a correctly shaped object holding one element where the
same file yields fifty-five, and nothing in the signature or the docstring
said so. The command line was unaffected because `__main__` materializes
the matches first.

    one-member ZIP, sbud()                       1 node
    one-member ZIP, sbud(matches=list(...))     55 nodes

A supplied iterable is now materialized too, not just the default: a
caller passing a generator has no way to know how far this needs it
drained, and before this it got the same truncated tree.

Closes #3584.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RG45aqyyCLkL5b1UB5GWd7
@ESultanik ESultanik self-assigned this Sep 18, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Analyzer.sbud() returns a truncated tree unless the caller materializes the matches first

1 participant