fix(parse): recognize .spawn() on a dict-of-functions Subscript (calque#189) - #190
Merged
Merged
Conversation
…ad of resolving to an empty target (calque#189) Found while investigating whether `calque spawn-run` could drive AI-Almanac's forecasts_app.py for real: its .spawn() call site is `SEASON_BUNDLE_FNS[_model_env(model_id)].spawn(...)` -- _attr_chain only walks Name/Attribute nodes, so the Subscript in the middle of the chain made the call site's target resolve to an empty string, invisible to spawn-run's target-keyed driver. pyast now tracks every function assigned into a module-level dict via `NAME[<key>] = some_function` (Collector._callable_dicts), regardless of what <key> is (a loop variable here, never a literal -- the real runtime-selected callable genuinely isn't resolvable statically). A .spawn() whose receiver is a Subscript on a known such dict lists every candidate instead of going silently empty. internal/parse.SpawnCallSites/SpawnCallSitesReport expand one SpawnCallSite per candidate (same call args, since the real selection can't be known) with a leak noting the expansion, factored into a pure spawnCallSitesFromInvokeCalls helper for unit testing without a pyast subprocess.
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
calque spawn-runcould drive AI-Almanac'sforecasts_app.pyfor real: its.spawn()call site isSEASON_BUNDLE_FNS[_model_env(model_id)].spawn(...)— a dict subscript, not a plain name.tools/pyast/pyast.py's_attr_chainonly walksName/Attributenodes, so this call site's target resolved to an empty string, silently invisible tospawn-run's target-keyed driver (internal/exec/spawnshard.go'sBuildSpawnManifests).NAME[<key>] = some_function(newCollector._callable_dicts), regardless of what<key>is — in this real-world case it's a loop variable, never a literal, so the actual runtime-selected callable genuinely isn't statically resolvable (that limit isn't fixed here, and shouldn't be — it's honest)..spawn()whose receiver is aSubscripton a known such dict now lists every candidate instead of resolving to nothing.internal/parse.SpawnCallSites/newSpawnCallSitesReportexpand oneSpawnCallSiteper candidate (same call args on each, since the real selection can't be known), with a leak noting the expansion — factored into a purespawnCallSitesFromInvokeCallshelper so the expansion logic is unit-testable without a pyast subprocess.cmd/calque/spawnrun.gonow usesSpawnCallSitesReportso this expansion leak lands in the same report the caller already prints.Files touched
tools/pyast/pyast.py—Collector._callable_dicts,visit_Assign's newNAME[<key>] = funcbranch,.spawn()handler's candidate lookup.internal/parse/parse.go—pyInvokeCall.Candidates,SpawnCallSitesReport,spawnCallSitesFromInvokeCalls.internal/parse/parse_test.go— new tests (subprocess-level + pure-Go expansion logic).cmd/calque/spawnrun.go— switched toSpawnCallSitesReport.testdata/scripts/spawn_dict_dispatch.py— new fixture mirroringforecasts_app.py's real shape exactly.CHANGELOG.md.Test plan
go build ./... && go vet ./... && gofmt -l . && go test -count=1 ./...— all passgolangci-lint run ./...— 0 issuesruff check .— all checks passedforecasts_app.py: the.spawn()call site now emits{'target': '', 'candidates': ['run_season_forecast_bundle'], ...}instead of{'target': '', ...}with no candidates key at all