Go: package-level constants and variables become nodes - #3098
Open
ehrlichandreas wants to merge 1 commit into
Open
Go: package-level constants and variables become nodes#3098ehrlichandreas wants to merge 1 commit into
ehrlichandreas wants to merge 1 commit into
Conversation
The Go extractor dispatched on four node types: function_declaration, method_declaration, type_declaration and import_declaration. Go's grammar also has const_declaration and var_declaration, so a package-level constant never became a node, and reading one was not recorded as a relation. Measured on a 59-file Go project, asking for fifteen constants each read in exactly one function: none of the fifteen constants was a node, while all fifteen of the reading functions were. `graphify query <constant>` answered "No matching nodes found" every time. The graph held the answer and offered no way in. Three parts: - const_declaration and var_declaration become nodes, marked with value_kind so later passes can find them, with a `contains` edge from the file. - Reading such a name inside a function body emits a `references` edge with context `value_use`. Without it the node exists but stays unreachable. Restricted to names declared as package-level values, so a local identifier produces nothing. - Names not declared in the file being extracted go to a new raw_value_refs bucket, bound afterwards by _bind_cross_file_value_refs. This mirrors raw_calls: an extractor sees one file and reports what it cannot resolve rather than guessing. Of the fifteen constants above, three were read from a sibling file, and those three stayed unreachable until this pass existed. Bound only for an unambiguous name, the same god-node guard the call resolver uses. raw_value_refs[].caller_nid is rewritten by both id_remap and sym_remap, as raw_calls[].caller_nid already is - left stale the edge would dangle on its source. After the change the same fifteen questions are answered 15/15. The graph grows from 327 to 395 nodes and 797 to 976 edges on that project, with no measurable change in build time (0.76 s both ways). tests/test_go_value_nodes.py covers both directions: a declared constant becomes a node, an unused one gets no reader, a cross-file read is bound, and a local variable neither becomes a node nor binds to one. Four of the six fail without this change. Co-Authored-By: Claude Opus 5 <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 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.
Problem
The Go extractor dispatches on four node types:
Go's grammar also has
const_declarationandvar_declaration. Apackage-level constant therefore never becomes a node, and reading one
is not recorded as a relation.
I hit this while comparing retrieval tools on a 59-file Go project,
using fifteen constants that are each read in exactly one function.
Every query returned the same thing:
Checking the graph rather than guessing: none of the fifteen
constants was a node, while all fifteen of the reading functions
were. The graph held the answer and offered no way in.
Change
Three parts, roughly 130 lines.
const_declarationandvar_declarationbecome nodes, marked withvalue_kind, with acontainsedge from their file.Reading such a name inside a function body emits a
referencesedgewith context
value_use. Without it the node exists but staysunreachable. Restricted to names declared as package-level values, so
a local identifier produces nothing.
Names not declared in the file being extracted go to a new
raw_value_refsbucket, bound afterwards by_bind_cross_file_value_refs. This mirrorsraw_calls: an extractorsees one file and reports what it cannot resolve rather than
guessing. Three of the fifteen constants were read from a sibling
file and stayed unreachable until this pass existed. Bound only for
an unambiguous name, the same god-node guard the call resolver uses.
raw_value_refs[].caller_nidis rewritten by bothid_remapandsym_remap, asraw_calls[].caller_nidalready is.Result
Same project, same fifteen questions,
graphify query --budget 2000:The jump-style questions this tool is built for are unaffected: 7/7
before and after on a separate 7-question set.
Tests
tests/test_go_value_nodes.py, six cases covering both directions: adeclared constant becomes a node, an unused one gets no reader, a
cross-file read is bound, and a local variable neither becomes a node
nor binds to one. Four of the six fail without this change.
Full suite: 25 failures before, 25 after (all pre-existing, in
test_terraform.pyandtest_skillgen.py), 4728 passing.ruff checkclean on the touched files.
Note on scope
This is Go only. Of the 30 extractors, three mention a constant node
type at all, so the same gap likely exists elsewhere - Rust has
const_itemandstatic_itemand neither is handled. I did not touchthose: the cross-file pass is language-agnostic, so another extractor
only needs to emit
value_kindnodes andraw_value_refsto use it.