Apply the same house-style cleanup as the English README: collapse
every em-dash run to a colon and replace the flag-emoji language
switcher with plain text language names, across all translated READMEs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- move the Documentation link hub below See-it-in-action, What-it-does,
and Benchmarks, so a skimmer sees real output and numbers before a
wall of external links
- let the outcome-bearing headings carry a true number already in the
body: 'Benchmarks: 0.497 recall@10, 0 LLM credits' and
'What files it handles: ~40 languages'
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- one 'one install, any assistant, any OS' line (fixes the 15+/20+/22
assistant-count mismatch to a consistent 20+)
- de-dupe the install steps: the quickstart lives in the hero; the
Install section now leads with install methods and the auto-detect
- state the Python/uv prerequisite inline at the hero
- sell 'graphify install' auto-detect (no per-assistant setup)
- trim the four per-platform deep-dives and the Project-configuration
cache/import prose out to docs.graphify.com
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace every ' — ' descriptor dash with a colon and swap the
flag-emoji language switcher for plain text language names, to match
the house style (no emojis, no em dashes).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fourteen community pull requests: C# call-resolution and id-collision
fixes, Python/Go/dedup correctness, a hub-label sidecar, a post-merge
heal hook, and query/build fixes. The AST cache schema is bumped (now
15), so the first build after upgrading re-extracts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Go generic receiver types are normalized before building the method's
parent id (#4438), so methods attach to the type node; this changes AST
extraction output and warm caches must re-extract.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The graph.json union merge driver can resurrect a symbol deleted on one
side of a merge; nothing re-extracts that file unless it is touched
again, so the phantom node persisted. graphify hook install now also
registers a post-merge hook that runs an unscoped background rebuild,
re-deriving the graph from the merged tree and dropping the stale node.
Install/uninstall/status go through the same hook machinery as the
post-commit/post-checkout hooks.
Squashed from #3556 with authorship preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hub-fallback community names were written straight into the tracked
.graphify_labels.json. They now go to a .graphify_labels.pending.json
sidecar (written atomically alongside the curated labels), so the
tracked file holds only curated (manual/LLM) names; promotion drops a
cid from pending and unlinks an emptied sidecar.
Squashed from #3551 with authorship preserved.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Preserve relationships between already visited nodes in BFS and DFS fallback traversal.
Collect edges from the induced subgraph of selected nodes while preserving directed edges, parallel relationships, and traversal depth limits.
Add regression tests for cycles, cross-links, multiple seeds, and output boundaries.
Fixes#4314
Co-authored-by: OpenAI Codex <codex@openai.com>
(cherry picked from commit fde445e263)
Review follow-up on #4429:
- The merge-time sweep now skips nodes referenced by a hyperedge in
G.graph; degree 0 does not mean unreferenced for them, and sweeping one
left a dangling member that to_json writes verbatim.
- On the prune path a source-less non-code, non-stub node is removed only
when the prune itself isolated it (v8's post-rebind _isolated_before), so
semantic nodes stranded by re-extraction survive as before.
Co-Authored-By: Grok Bot <grokbot@users.noreply.github.com>
(cherry picked from commit 685f7f47bb)
build_merge drops re-extracted sources' nodes and edges by source_file, so a
source-less placeholder (external type, namespace stub) whose last edge went
with them survived at degree 0. The #2807 sweep only ran under prune_sources,
and its 'already isolated' set was taken after the old edges were gone.
Snapshot the source-less nodes isolated in the stored graph (plus ones the
fresh chunks emit isolated) before the rebind, and run the sweep after every
merge, sparing only that snapshot. Off the prune path only code placeholders
and #2873 external stubs are collected, so semantic/curated nodes are safe.
Swept disk nodes are counted in the #3774 shrink accounting.
Fixes#4399
Co-Authored-By: Grok Bot <grokbot@users.noreply.github.com>
(cherry picked from commit bd1f0acf9b)
(b *Box[T]) reads its receiver's type field as "Box[T]" verbatim, but
the type declaration's own id is keyed on the bare name alone: type
Box[T any] struct {...} captures [T any] as a separate
type_parameter_list, never part of the name. Without stripping the
instantiation before that text turns into an id, every method on a
generic type attached to a node disconnected from the type itself --
Box's own node carried no method edges at all, even though
go_type_table-based call resolution could still reach a method through
the receiver's own-function self_type binding.
Adds _go_strip_generic_args and applies it everywhere a receiver's raw
type text is turned into an id: the live walk() pass that attaches a
method to its owner, and the separate case-collision pre-scan
(_receiver_type_of) that has to agree with it or case-only-collision
salting silently breaks for generic types.
Found while verifying the already-merged `go_type_table` member-call
resolution (deca9fa3 / cherry-picked as 1695efae) against the edge
cases from #3368 -- this is the one gap left in that fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 1a8a04ac50)
make_id strips leading/trailing underscores and casefolds, while C# is
case-sensitive and spells private members `_Name`. In one file,
`Action()` / `_Action()`, `Size` / `_Size`, `Load()` / `load()` and the
types `Pair` / `_Pair` minted one id; add_node kept the first declaration
and dropped the second, and the dropped method's calls were credited to
the survivor.
Apply the rule of #3302 to C#: a pre-scan groups the declared names of a
file (types; per type its methods, properties and enum members) that
would share an id, and _underscore_salted_nid (renamed from the Python
helper, unchanged) keeps the plain id on the one name without a leading
underscore and salts the rest. In-file type references and base lists go
through the same rule. A name without a colliding sibling keeps its id.
The C# member-call resolver keyed names by lowercased alphanumerics, so
`this.Put()` bound to `_Put()` on a nearer base, and `Token` / `TOKEN`
were one type key. The key now keeps `_` and case.
Fixes#4415.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 914dda60cf)
A C# `foreach` loop variable had no binding in the method-scoped receiver
table, so a call on it (or a chain starting at it) never resolved across
files: `foreach (var a in agents.Values) a.look.Animate();` produced no edge.
The loop variable is now bound inside its loop: to its explicit type
(`foreach (Agent a in xs)`), or for `var` to what a class field or property
yields when iterated: the element of a `T[]` or of a sequence type, and a
dictionary's value or key type for `f.Values` / `f.Keys`. Iterating a
dictionary itself (KeyValuePair), nested collections, type parameters, and a
loop over a name that a local or parameter shadows stay untyped.
Closes#4420.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f101ad4913)
A C# object creation was resolved against every definition named X, methods
included. Inside a helper like `static Bounds Bounds(..) => new Bounds(..)`
(Unity's Bounds, outside the corpus) it became an EXTRACTED self-loop; across
files the bare-name pass bound every `new List<T>()` in the corpus to an
unrelated `List()` method, minting an INFERRED hub with one false caller per
construction site.
The same-file path now defers a construction whose bare-name target is a
method, and the cross-file pass drops method candidates for `csharp_new`
calls, next to the enum-member/property exclusion (#3815, #4245). A real
type with the same name still wins.
Dropping those candidates removes ties that used to hide a separate guess: a
qualified construction (`new QuickSlotsBar.Slot()`) with several bare-name
`Slot` classes went to the path-proximity tie-break, which ignores the
qualifier and picks the Slot next to the caller. A qualified construction now
takes the one candidate nested in a type named by the qualifier's last
segment, or gets no edge; namespace qualifiers stay with
`_resolve_csharp_qualified_calls`.
Fixes#4419.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 68202f9766)
A C# member call whose receiver has no capturable name — a member chain
(`run.Sequence.Apply()`, `s.Items.Count()`), an invocation (`Make().Apply()`)
or a `new X(...)` — reached the per-file bare-name map, because the C# defer
rule required `member_receiver`. It then bound to the caller's own same-named
method as an EXTRACTED edge: a false self-loop (`a.look.LateUpdate()` inside
`LateUpdate()`), LINQ `Count()` bound to a local `Count()`, or a typed chain
landing on the wrong class instead of reaching `receiver_chain` resolution.
Every C# member call now defers. A typed chain still resolves through
`receiver_chain`; `new X(...).M()` (#1770) is typed from the construction so
it keeps its edge; anything untypable gets no edge (fail-closed).
Fixes#4418.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d5544b125f)
Two files can mint the same stem (`Spec.cs` / `_Spec.cs`, `Cfg/Helpers.cs` /
`Cfg_Helpers.cs`), so the type ids in them collide. The C# partial merge ran
before _disambiguate_colliding_node_ids: it rewired the edges of every half
onto the canonical half's still-colliding id, and the disambiguation, which
remaps by (id, source_file of the edge), then missed every edge that came from
another half and left it on a dead id. The members of that half had no owning
type. When both colliding files declared a half of the same partial type, the
second node kept the canonical id, was not removed by the merge, and was split
off again, so the type stayed two nodes. And since the merge removes
merged-away halves by id, an unrelated node in another file that shared such
an id was removed with them.
Run the disambiguation first. Every edge is still on its own half when ids are
salted, and the merge joins the halves by type path, whatever their ids.
Fixes#4411.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 78575576a8)
The bug:
Jinja2 templates were completely skipped by Graphify's detection phase because their extensions were not listed in the recognized document extensions. This resulted in the source for generated files being hidden from the graph.
The fix:
Added `.j2`, `.jinja`, and `.jinja2` to the `DOC_EXTENSIONS` set in `graphify/detect.py`.
Tests:
`tests/test_detect_jinja.py` added to verify the newly added Jinja extensions are accurately recognized as documents. This ensures the bug does not regress in future updates.
(cherry picked from commit 61f6a40a56)
With protocol.md next to docs/protocol.md and docs/firmware/protocol.md, all
three minted `protocol` and the label tiebreak dropped a real document.
A file whose full stem is the id now defines it and keeps the bare id; the
other files are path-qualified when they have no other node spelled with
their own full stem. If they do, the bare id may be a reference, so the file
keeps today's collapse (known boundary). A genuine reference to the top-level
file still collapses onto it.
A bare path slice from a file that has its own page ranks as a reference, so the
real file survives whatever the labels are.
(cherry picked from commit a3fa3c47c4)
Sixteen community pull requests: cross-repo member-call resolution
(Go/PHP/TS/Kotlin/ObjC), generic/template type-param cleanup
(Kotlin/Scala/C++/Swift), an Objective-C enum extractor, interface
dispatch fan-out, a native Pi guard extension, and resolution/ingest
fixes. The AST cache schema is bumped (now 13), so the first build
after upgrading re-extracts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`_resolve_objc_member_calls` types a receiver from `self`/`super`, an explicit
class name, the file's local table or the class's @property/ivar table, then
requires exactly one declaration of that type. Zero declarations and more than
one shared a single bail, so a call into a class another repository declares was
dropped with the receiver type already in hand — the case #3152 parks for the
merge in Java, C++, C# and Swift.
Split the three type-known bails: zero declarations parks `{callee,
receiver_type, lang: "objc", line}` on the caller, more than one stays dropped as
before (a local ambiguity that merging only widens). A builtin receiver type is
filtered out at the parking sites, matching what the Swift and C++ resolvers
already skip before resolving.
On the merge side an ObjC method label keeps its `+`/`-` sigil where every other
extractor writes `.name()`, and a parked selector does not say which was meant,
so both spellings are tried and a class declaring both counts as ambiguous. The
sigil is what keeps ObjC and C++ members disjoint inside a shared `.h`, which is
in both languages' suffix sets.
Depends on the `_callable` / `_callable_class` markers (#3228) that
`_index_declarations` indexes declarations by; the commit before this one carries
them.
(cherry picked from commit 487fd5490d)
Every other extractor stamps its definitions through the generic engine:
`_callable` for "a real callable, not a same-named data symbol" (#2438),
narrowed to `_callable_class` for a type, which is callable only through a
constructor (#2137). The ObjC extractor builds its nodes by hand and set
neither, so an ObjC class was invisible to every pass that indexes
declarations by those markers — the `same_type_as` pass from #3007, the
qualified-name indexes, and anything new that needs to tell a declaration
from a reference — and an ObjC method could not be told apart from a data
symbol of the same name.
`@interface`, `@implementation` and `@protocol` nodes now carry both
markers (a protocol is a type declaration, the same way the engine marks a
Java or C# interface), and method nodes carry `_callable`. The file node
and the stubs minted for dangling references stay unmarked: a stub has no
declaration behind it.
(cherry picked from commit 9a64af9be1)
The cross-file `_resolve_kotlin_member_calls` landed for #1698 binds
`Receiver.method()` only when the object/class the receiver names is declared
in the same build. When that declaration lives in another repository the pass
had the receiver name in hand and dropped the call anyway, so nothing about it
reached graph.json — the one artifact merge-graphs and global add read — and no
merge-time pass could recover what was never written down.
Park the call by name at that bail-out, the same mechanism Java/C++/C#/Swift
already use: only when the object is declared nowhere here (an ambiguous 2+ match
still fabricates nothing) and never for a builtin receiver. cross_repo_calls then
finishes the edge once the declaring repo joins the graph, gated on .kt/.kts so a
same-named declaration in another language cannot answer a Kotlin call.
Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit fa193c96c2)
A corpus-wide type index let a same-named class in another language stand in for
the receiver's type, which hid from the parking branch that nothing in the JS/TS
family declares it. SFC script blocks are in the family, so a class exported from
a .vue file still answers — here and at merge time.
(cherry picked from commit 078c106e62)
`_resolve_typescript_member_calls` binds `this.repo.save()` only when the
receiver's type is declared in the same build. With the type in another
repository the resolver held the receiver type and dropped the call, so
`graph.json` — the only artifact `merge-graphs` and `global add` read — recorded
nothing and no merge-time pass could recover it.
Those calls are now parked on the caller as `metadata.unresolved_calls`, and
`link_cross_repo_member_calls` finishes them after the merge. One
`_LANG_SUFFIXES` key covers all eight TS/JS suffixes: a TS class legitimately
answers a JS call site, and two keys would drop every TS<->JS cross-repo call.
Only a table-typed receiver parks. The uppercase-spelling arm also matches
namespace aliases and default imports (`React.createElement()`), and
`node_modules` is never scanned, so parking those would send npm names to the
merge to be matched by bare name. The park sits strictly upstream of the #2553
origin gate, which only ever vets a name that did match locally.
(cherry picked from commit 5b593c6d00)
`_DISPATCH` routes `.mjs`/`.cjs` to `extract_js` and the cross-file call gate
already lists them, but `typescript_member_calls` claimed only six suffixes, so a
repo written entirely in `.mjs` was the one shape where the resolver never ran and
`const s = new Svc(); s.doThing()` produced no edge.
Both the suffix set and the resolver now read one `_JS_TS_SUFFIXES` constant.
(cherry picked from commit 84fce94ce8)
A receiver typed to a class this build declares nowhere is parked on the
caller (#3152); the merge then binds it to the one declaration in another
repo. PHP was absent from `_LANG_SUFFIXES`, so parked PHP entries were never
language-checked and the edge was dropped. Adding PHP's suffixes lets the
merge finish the call, gated to PHP declarations like every other tier.
Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit e7fee26542)
v8's `_resolve_php_member_calls` (#3872) binds scoped `Class::method()` calls
but leaves instance `$g->method()` calls unresolved. This adds the instance
tier beside it: a per-file receiver type table (`_php_receiver_type_table`
over typed properties, promoted constructor params, typed params and `new`
bindings) names the receiver's class, and the shared resolver takes the single
PHP declaration of that class and emits the `calls` edge, INFERRED.
The declaration index is gated to the PHP interop family: the index is
corpus-wide, so a same-named class in another language would both answer the
receiver and hide that no PHP file declares it. A receiver the table cannot
type to one class — untyped, union, longer chain — stays unresolved.
Co-Authored-By: Claude Code <noreply@anthropic.com>
(cherry picked from commit 199ba1af0d)
test_the_first_binding_of_a_name_wins asserted the flat table's cross-function
shadowing as intended behaviour, which the scoping fix removed; its fixture now
pins that each function's parameter answers only its own calls.
(cherry picked from commit 8a4bfef899)
Parameter names repeat across a Go file, so the file-flat table answered one
function's receiver with a sibling function's parameter type: `Second(g *Shouter)`
calling `g.Greet()` bound to Greeter.Greet. Each function body now carries its own
parameter/local table, consulted before the flat one, which keeps serving fields.
(cherry picked from commit b74abf098a)
A corpus-wide `_callable_class` lookup let a same-named class in another language
answer a Go receiver, and it hid from the parking branch that no Go file declares
the type. The flat per-file table also mistyped a method's own receiver when an
earlier binding of the same name won, so `s.Save()` inside a `Store` method could
reach `Server.Save`.
(cherry picked from commit 86792724ff)
The Go resolver had the receiver's type in hand and dropped the call when nothing
in the corpus declared it, so `graph.json` — the only artifact `merge-graphs` and
`global add` read — recorded nothing and no merge-time pass could recover it. A
merged graph of a Go service and a Go library was missing exactly the edges that
make it a call graph.
Parks the pair on the caller node like the Java/C++/C#/Swift resolvers already do,
and adds `"go": {".go"}` to the merge pass's language guard. `.go` alone: Go and
Java both turn up in one backend merge, and a shared type name across them is a
coincidence, not a namespace.
The `_key` case preservation the merge pass already documents is what makes this
correct for Go — `greet` is a different method from `Greet` and is not reachable
from another package at all.
(cherry picked from commit f7d2a50603)
`g.Greet()` where `Greeter` is declared in another file produced no edge. The
shared cross-file pass skips member calls, and the Go extractor read the
receiver's identifier only to discard it for anything that was not an imported
package — so the one shape Go dependency injection actually takes (a struct
field used from a method) contributed nothing to the call graph.
`extract_go` now exports a per-file `go_type_table` built from the four places a
Go receiver's type is written down: a struct field, a parameter (which covers the
method receiver `func (s *Server)`), `var x T`, and `x := T{}` / `x := &T{}`.
`_resolve_go_member_calls` looks the receiver up there, takes the single
declaration of that type and emits the `calls` edge to its method.
Also stamps `_callable` / `_callable_class` on Go declarations (#2438). Go is the
one extractor outside the tree-sitter engine, so its nodes carried neither, which
left them invisible to every consumer that reads the markers off the node.
Design notes:
- The receiver travels as `member_receiver`, not `receiver`: in this extractor
`receiver` means "imported package", and the Swift/Python/Ruby member-call
resolvers select on `is_member_call` plus `receiver` without checking the
language, so a Go name there binds to their types in a mixed corpus.
- `s.logger.Log()` is read as a call on the `logger` field only when `s` is the
enclosing method's own receiver. The table is flat and file-scoped, so any
other head could be a package or a variable whose type it cannot confirm.
- Names are matched case-sensitively, unlike the sibling resolvers. Go exports by
capitalisation, so `Run` and `run` on one type are two different methods.
- Go does not defer: a member call reaches `raw_calls` only once the bare callee
name misses in the caller's file, so in-file behaviour is byte-identical.
- Two packages declaring the same name stay two type nodes and the
single-definition guard bails — without import evidence neither is the answer.
(cherry picked from commit deca9fa366)
extract_go keys a type on its package directory, so every file declaring a
method on it mints the type again under the same id. Disambiguation then splits
those apart by path, and the type ends up fragmented into several partial nodes,
each owning some of its methods: the declaring file's node owns none of them,
and every single-definition guard downstream reads the set as an ambiguity.
Fold the id-collision before disambiguation, the same point and the same shape
as _merge_decl_def_classes for C/C++/ObjC. Guards: all members must be .go
files in one directory (the id folds in only the directory's name), and a
_test.go member stops the fold, since its package clause may be a separate
external test package. The declaring file wins; no edge re-pointing is needed
because the group already shares one id.
(cherry picked from commit 7f9439ddd5)
Validate both complete argument spans and reject unsupported body children instead of emitting partial enum graphs.
AI-Assistance: OpenAI Codex
(cherry picked from commit 7c09b4d360)
Extract NS_ENUM and NS_OPTIONS typedefs in Objective-C files using isolated enum body parsing and original member locations.
Fixes#4400
AI-Assistance: OpenAI Codex
(cherry picked from commit d6a74da475)
A `struct Stack<Element>` / `func map<R>(...)` binds `Element`/`R` as local
names, not corpus symbols, but `_swift_collect_type_refs` appended the bare
identifiers used in a signature or stored-property type as type references and
ran them through the sourceless-stub path. Every generic type/function in the
corpus then collapsed its `T` onto one bare `t` node — a god node that pollutes
god-node rankings, since single-letter type parameters are not in the
analyzer's builtin-noise filter. C#, Java, TypeScript, Kotlin, Scala, C++, and
Go already exclude theirs.
Mirrors that pattern (and complements the `Self` keyword fix already in this
collector): a new `_swift_type_parameters_in_scope` helper walks up to every
enclosing declaration's `type_parameters` list and takes each `type_parameter`'s
first `type_identifier` as the bound name (a constraint like `T: Equatable` is a
separate `user_type`, left to reference), and `_swift_collect_type_refs`
auto-computes and threads that skip set. Genuine user types and the `Int`/
`String` builtin hubs are unaffected.
(cherry picked from commit 2e8a5c0f97)
A `template<typename T>` binds `T` as a local name, not a corpus symbol, but
`_cpp_collect_type_refs` appended the bare identifiers used in a template
signature or member as type references and ran them through the sourceless-stub
path. Every template in the corpus then collapsed its `T` onto one bare `t`
node — a god node that pollutes god-node rankings, since single-letter type
parameters are not in the analyzer's builtin-noise filter. C#, Java,
TypeScript, Kotlin, Scala, and Go already exclude theirs.
Mirrors that pattern: a new `_cpp_type_parameters_in_scope` helper walks up to
every enclosing `template_declaration`'s `template_parameter_list` and collects
names from its `type_parameter_declaration`/`variadic_type_parameter_declaration`
children (a non-type `int N` parameter is a value and is left alone), and
`_cpp_collect_type_refs` auto-computes and threads that skip set. Genuine user
types and the generic container head still reference.
(cherry picked from commit e3bf52b318)
A `class Box[T]` / `def map[R](...)` binds `T`/`R` as local names, not
corpus symbols, but `_scala_collect_type_refs` appended the bare identifiers
used in a signature as type references and ran them through the sourceless-stub
path. Every generic class/method in the corpus then collapsed its `T` onto one
bare `t` node — a god node that pollutes god-node rankings, since single-letter
type parameters are not in the analyzer's builtin-noise filter. C#, Java,
TypeScript, Kotlin, and Go already exclude theirs.
Mirrors that pattern: a new `_scala_type_parameters_in_scope` helper walks up
to every enclosing declaration's `type_parameters` list (the use site is
`type_arguments`, a distinct node) and collects parameter names from direct
identifiers and variance-annotated params, skipping `context_bound`/
`upper_bound`/`lower_bound` nodes (those name real types). `_scala_collect_type_refs`
auto-computes and threads that skip set. Context bounds (#2046), genuine user
types, and the generic container head still reference.
(cherry picked from commit 585e909535)
A `class Box<T>` / `fun <R> map(...)` binds `T`/`R` as local names, not
corpus symbols, but `_kotlin_collect_type_refs` appended the bare identifiers
used in a signature or property type as type references and ran them through
the sourceless-stub path. Every generic class/function in the corpus then
collapsed its `T` onto one bare `t` node — a god node that pollutes god-node
rankings, since single-letter type parameters are not in the analyzer's
builtin-noise filter. C#, Java, TypeScript, and Go already exclude theirs.
Mirrors the C#/Java pattern: a new `_kotlin_type_parameters_in_scope` helper
walks up to every enclosing declaration's `type_parameters` list (the use site
is `type_arguments`, a distinct node, so an instantiation argument is never
collected), and `_kotlin_collect_type_refs` auto-computes and threads that skip
set. Genuine user types and the generic container head still reference.
(cherry picked from commit a9304a245c)
_dispatch_confidence was computed from the raw implementers set before
the per-implementer language gate ran, so a sourceless/external-reference
implementer, or a cross-language name collision, that never produces an
edge could still dilute the confidence score for the implementer(s) that
do. A single real implementer alongside one gate-failing entry scored
0.75 (the two-implementer tier) instead of 0.85.
Gate first, then count only the implementers that pass it, so the score
reflects how many implementers actually share the dispatch edge.
Addresses review feedback on PR #4396 (Graphify review bot, finding at
interface_dispatch.py:154).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit b01a6fcf10)
resolve_interface_dispatch (shared by the C# and Swift gates) bailed out
entirely whenever an interface/protocol had more than one implementer,
on the reasoning that guessing which one a runtime call reaches would be
wrong. For a static structural graph the question isn't which one is
reached at runtime, it's which ones *can* be reached, and the answer with
N implementers is all N, not none -- so every chain through an injected
dependency with more than one implementation was left permanently cut at
the interface.
Each implementer is still subject to the existing single-owner guard
independently: an implementer that itself declares more than one method
of the same name is skipped on its own, without suppressing its siblings.
Confidence drops as the candidate set grows, using the extraction
rubric's own discrete INFERRED tiers (0.85 / 0.75 / 0.65 floor) instead of
inventing continuous precision.
Fixes#3800 for the dispatches_to half (interface->implementation calls).
The property-access half of that issue (a method edge for `c.Count`
through an interface-typed receiver) is a separate mechanism and is not
addressed here, to keep this PR to one concern.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit f340494e07)