You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Python: a quoted forward-reference annotation (o: "Alpha") drops the call edge that the identical unquoted annotation resolves #1684
Python: a parameter annotated with a string (forward-reference) annotation — def f(o: "Alpha") — loses receiver-type resolution that the identical unquoted annotation gets. The method call produces no calls edge at all.
Quoted annotations are ordinary Python (forward references, and everything under from __future__ import annotations), so this silently drops real edges in normal code.
Environment
@colbymchenry/codegraph1.5.0 and 1.6.0 (npm latest), Linux, fresh codegraph init per version.
caller target line
bare render @pkg/a.py:6 9
(quoted produces no row at all)
Same file, same call expression, same class — only the quoting of the annotation differs.
Note on the trigger
The miss needs a competing same-named method to be present: with only Alpha.render (plus the top-level render) in the tree, the quoted form still resolved. Adding Beta.render is what makes the quoted form drop out while the bare form keeps resolving. So the quoted annotation appears not to establish the receiver type, leaving the call to name matching, which then declines under the extra candidate.
codegraph callers render correspondingly under-reports: quoted never appears.
Verification
Reproduced on both versions with a fresh index each time. Controls in the same run: the unquoted form resolves, and in a two-candidate variant of the same fixture the quoted form resolves — so this is the quoting interacting with candidate count, not a blanket annotation failure.
Additional evidence, replacing the speculative wording in my "Note on the trigger" above with what the index actually records.
The call is extracted — it is not a missing ref. On the same fixture, unresolved_refs holds:
from_node
reference_name
kind
line
candidates
status
quoted (pkg/b.py)
o.render
calls
5
(empty)
failed
So the pipeline reaches resolution with the receiver intact and then fails with zero candidates, while the unquoted bare on line 8 resolves to Alpha.render. That narrows it to receiver-type resolution not accepting a string annotation, rather than an extraction-side drop.
Two consequences worth separating:
The edge is lost — codegraph callers render reports only bare, never quoted.
The loss is not visible to the caller.unresolved_refs.status='failed' records that the index knows this call could not be resolved, but the query surface returns an ordinary, unqualified result — indistinguishable from a symbol that genuinely has one caller. Anything reasoning about blast radius reads a confident answer over a known gap.
Point 2 is arguably the more serious half and is not specific to this bug; happy to split it into its own issue if you would rather track it separately from the annotation handling.
Verified on 1.5.0 and 1.6.0, fresh index each, cross-checked against both the CLI and direct SQL.
Reproduced on Linux against current main: with competing same-named methods (Alpha.render / Beta.render / top-level render), def quoted(o: "Alpha"): return o.render() produced no calls edge while the unquoted twin resolved to Alpha::render.
Root cause: Python buildLocalReceiverTypePatterns only matched o: Alpha, so a quoted forward reference (o: "Alpha" / o: 'Alpha', including everything under from __future__ import annotations) contributed no receiver type and resolution failed with zero candidates.
Fix (same approach as #1690, rebased surgically onto current main): admit the quoted annotation pattern before the unquoted one. Regression in __tests__/python-quoted-annotation.test.ts fails on main and passes with the fix; resolution.test.ts 198/198 green.
Closing as completed. Thanks @inth3shadows for the tight repro and the unresolved_refs evidence.
Activity
inth3shadows commentedon Sep 4, 2026
Additional evidence, replacing the speculative wording in my "Note on the trigger" above with what the index actually records.
The call is extracted — it is not a missing ref. On the same fixture,
unresolved_refsholds:quoted(pkg/b.py)o.rendercallsfailedSo the pipeline reaches resolution with the receiver intact and then fails with zero candidates, while the unquoted
bareon line 8 resolves toAlpha.render. That narrows it to receiver-type resolution not accepting a string annotation, rather than an extraction-side drop.Two consequences worth separating:
codegraph callers renderreports onlybare, neverquoted.unresolved_refs.status='failed'records that the index knows this call could not be resolved, but the query surface returns an ordinary, unqualified result — indistinguishable from a symbol that genuinely has one caller. Anything reasoning about blast radius reads a confident answer over a known gap.Point 2 is arguably the more serious half and is not specific to this bug; happy to split it into its own issue if you would rather track it separately from the annotation handling.
Verified on 1.5.0 and 1.6.0, fresh index each, cross-checked against both the CLI and direct SQL.
colbymchenry commentedon Sep 8, 2026
Fixed in #1770.
Reproduced on Linux against current
main: with competing same-named methods (Alpha.render/Beta.render/ top-levelrender),def quoted(o: "Alpha"): return o.render()produced nocallsedge while the unquoted twin resolved toAlpha::render.Root cause: Python
buildLocalReceiverTypePatternsonly matchedo: Alpha, so a quoted forward reference (o: "Alpha"/o: 'Alpha', including everything underfrom __future__ import annotations) contributed no receiver type and resolution failed with zero candidates.Fix (same approach as #1690, rebased surgically onto current
main): admit the quoted annotation pattern before the unquoted one. Regression in__tests__/python-quoted-annotation.test.tsfails onmainand passes with the fix;resolution.test.ts198/198 green.Closing as completed. Thanks @inth3shadows for the tight repro and the
unresolved_refsevidence.fix(resolution): read a quoted Python annotation as a receiver type (#…