[pull] v8 from safishamsi:v8#99
Merged
Merged
Conversation
`graphify affected` (blast radius) was blind to a function passed BY NAME as a call argument — `executor.submit(fn)`, `Thread(target=fn)`, `map(fn, xs)`, callbacks — so those callers were silently dropped from the affected set (an under-counting blast radius reads complete while missing exactly where regressions hide). Capture them as a distinct `indirect_call` relation (INFERRED, context "argument"), kept separate from `calls` so strict call-graph queries stay precise, and add it to DEFAULT_AFFECTED_RELATIONS. Hardened over the original PR against the name-collision false edge (the Doxygen #3748 trap): emit only when the argument identifier resolves to a callable definition AND is not shadowed by a parameter or local binding in the enclosing function. So `def via(pool, handler): pool.submit(handler)` (handler is the param, not the module function) and `process(config)` where `config` is local data emit no edge, while a genuine module function passed by name still resolves. Dedup-safe against existing direct `calls` edges. Python only; dict-literal / getattr-by-string / decorator dispatch deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#1565 captured a function passed by name as a call argument (executor.submit(fn), Thread(target=fn), map(fn, xs)) only when the callback was defined in the SAME file — it resolved through the in-file label map. But the dominant real-world shape is cross-module: the callback is imported (`from .handlers import on_event; pool.submit(on_event)`), so the same-file map can't see it and the edge was dropped — exactly the caller a blast-radius query must not miss. When the argument identifier isn't defined in-file (and isn't shadowed by a param/local — that guard already ran), emit an `indirect` raw_call and let the existing cross-file resolution pass handle it, branching to a distinct indirect_call/INFERRED edge instead of calls. It rides the same single-definition god-node guard and import-evidence disambiguation as direct calls (parity: when a direct call resolves, so does the indirect one; when the name is ambiguous, both bail). Two added safeguards: the target must be a real callable def (per-file callable_nids unioned across the corpus) so an imported data constant can never become a dispatch target, and an existing direct `calls` edge for the pair pre-empts it. Smoke-verified: imported single-def callback resolves; ambiguous name bails (same as direct); imported data constant rejected; direct+indirect to the same target keeps only the direct edge; cross-file keyword target=cb resolves. Full suite 2710 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Slice 1 of #1566. A function referenced as a VALUE in a dict/list/set/ tuple literal — the registry/route-table idiom (`ROUTES = {"create": create_user}`, `HOOKS = [on_start, on_stop]`) — is an indirect dependency that blast-radius must see, but the call-argument capture (#1565) didn't cover it. Emit an indirect_call edge for each callable value: - module-level tables attribute to the file node; function-scoped tables attribute to the enclosing function. Same-file and cross-file (an imported handler in a table routes through the cross-file resolver). - dict KEYS are excluded (only values are references); non-callable values (a number, a string) never resolve; a name shadowed by a param/local or rebound at module scope is the local value, not the function. Refactors the resolve-and-emit logic shared by the argument and table paths into one guarded helper (_emit_python_indirect_ref) and threads the edge `context` ("argument" | "collection") through the cross-file pass. Adds _python_module_bound_names for the module-scope shadow set. 7 new tests (module dict/list, function-scoped, dict-keys-excluded, non-callable-value, module-reassign shadow, cross-file table). Full suite 2717 passed. Python only; assignment/return refs + other languages remain on #1566. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the indirect_call model to JavaScript and TypeScript, the next-
biggest callback-passing ecosystem. Now captured:
- callbacks passed by name as call arguments: `arr.map(fn)`, `setTimeout(fn)`,
`el.addEventListener("x", fn)` — function-scoped, attributed to the caller.
- module-level callback registration (idiomatic in JS, unlike Python):
Express routes `app.get("/", handler)`, event wiring `emitter.on("e", h)`,
timers — attributed to the file.
- object/array dispatch tables: `const ROUTES = { create: handler }`,
`const HOOKS = [onStart, onStop]`, object shorthand `{ handler }`.
Arrow-const functions (`const cb = () => {}`) are registered as callable
targets (threaded callable_def_nids + local_bound_names through
_js_extra_walk). Guards mirror Python: shadowing by param/local/module
reassignment is rejected (JS module shadow set excludes function-valued
declarators so arrow-consts stay resolvable), object KEYS and non-callable
values are excluded, inline arrows/function expressions are direct defs not
references, single-definition god-node guard cross-file.
Two cross-file fixes the JS import model exposed:
- a name that resolves to an import-surfaced FOREIGN symbol (JS named
imports map the real node into the importing file's label map) is now
deferred to the cross-file resolver instead of being mistaken for a local
non-callable; the global callable_nids guard still rejects imported data.
- indirect_call dedup is now call-aware: a benign `imports` edge from a file
to the symbol it imports no longer suppresses the indirect_call to it
(only a real calls/indirect_call edge does).
Shared the resolve-and-emit helper across Python and JS/TS. 9 new tests
(func-arg, module object/array, Express-style registration, inline-arrow
negative, param-shadow, key/data exclusion, shorthand, cross-file import,
TS typed params). Full suite 2726 passed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_check_skill_version advised "Run 'graphify install' to update" on ANY version mismatch. But `install` writes the package's OWN bundled skill and re-stamps .graphify_version, so when the skill on disk is NEWER than the running package, following that advice silently DOWNGRADES the skill to silence the warning. The docstring even said "warn if the skill is from an OLDER version" but the code never checked direction. Compare versions numerically (new _version_tuple, so 0.10 > 0.9 and a malformed stamp degrades instead of raising). When the skill is newer than the package, recommend upgrading the package (uv tool upgrade / pip install -U) instead of install; the older-skill case is unchanged. Hit in practice by a stale `uv tool` CLI and by contributors whose dev checkout stamped a newer skill. Reported by @TPAteeq. 4 tests (numeric ordering, both mismatch directions, silent-when-equal). Full suite 2730 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lice 2) A function bound to a name (cb = handler) or returned from a factory (def make(): return handler) is a real reference, but indirect_call only covered call arguments and dispatch tables, so `affected` still dropped these callers. Emit indirect_call (context "assignment"/"return", INFERRED) for the value-side identifiers of a Python assignment RHS and a return, at function scope (owner = enclosing function) and module scope (owner = file node). Reuses the shared _emit_indirect_ref guard. Scans the VALUE side only -- the assignment target is a new local binding, not a reference -- so the existing param/local shadow guard still rejects the false edges #1565 fixed. Negatives covered: param-shadow, local-shadow, non-callable emit nothing. Full suite green; ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )