Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

Rust extractor: lossy structure and name-only call resolution (generic impls, traits, mods, use-lists, receivers)

Open
#4,285 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
18/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
python, rust
Domain
devtools

Research direction

Start in graphify/extract.py at extract_rust, plus _make_id, _file_stem and the label_to_nid lookup in extract(), and reproduce with the sample file in the issue. The author has a patch and 10 regression tests, so the work is mostly reviewing the design: scoped IDs, trait impls, receiver-based call resolution and cross-file resolution. Done means the sample's orphan nodes, merged IDs and name-only edges are gone, and the regression tests pass.

Written by the indexing model from the issue text.

Description

Version: graphifyy 0.7.0 (graphify/extract.py, extract_rust)

extract_rust loses most of the structure of idiomatic Rust. Reproduced with this file:

use std::fmt::{self, Display};
pub trait Reader { fn read(&self) -> u8; fn skip(&self) { helper(); } }
pub struct Foo<T> { x: T }
impl<T> Foo<T> { pub fn new(x: T) -> Self { Self { x } } pub fn go(&self) { Self::new(1); helper(); } }
impl Display for Foo<u8> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Ok(()) } }
impl Reader for Foo<u8> { fn read(&self) -> u8 { 0 } }
mod inner { pub fn helper() {} pub struct Foo; }
pub fn helper() { inner::helper(); println!("x"); }
pub type Alias = u8; pub const K: u8 = 1; macro_rules! m { () => {} }

Structure problems

  1. Generic impls create orphan nodes. impl<T> Foo<T> / impl Foo<u8> produce nodes Foo<T> / Foo<u8>, separate from the Foo struct node. Methods hang off the orphans.
  2. Trait is dropped from impl Trait for Type. No implements edge; trait impls look like inherent impls, and several impls of the same method name for one type (e.g. three impl From<_> for E) share one ID scheme.
  3. trait_item returns without descending. Trait methods produce no nodes, so calls from default method bodies (skip -> helper) are lost.
  4. IDs ignore the inline mod path, and are case-folded. inner::helper and top-level helper get the same ID, as do inner::Foo and Foo; nodes merge and inner::helper() becomes a dropped self-call. Because _make_id lower-cases, const ITEM and fn item() also collide (first label wins). Across files, _file_stem is parent.stem, so every src/lib.rs shares src.lib.
  5. Missing node kinds: mod, type, const, static, union, macro_rules!.
  6. use handling is lossy: use std::fmt::{self, Display} yields one edge (the text before {), losing Display; nested/aliased lists are not flattened.

Call-resolution problems

The extractor resolves calls by bare name only (label_to_nid[name.lower()], last definition wins), ignoring qualifiers and receivers, so on a real workspace most edges for common names are wrong. Examples found by auditing the old output line by line:

  • Vec::new(), Cursor::new(..), HashMap::new(), u64::from(x), Default::default() are linked to any local fn new / fn from / fn default.
  • v.len() / s.is_empty() / file.seek(..) on std types are linked to local methods with the same name.
  • Value::Inline(..) / VrIndex(..) (enum variant / tuple-struct constructors) are linked to a function named inline / the type node.
  • validator.enter_item(..) is linked to an unrelated free fn enter_item, not the method on validator's type.
  • Conversely, true calls are missed: self.m() when several types in the file define m, Type::f() when Type has several impls, trait-qualified calls (Seek::seek(&mut s)), calls through generic bounds (V: Trait), if let Some(x) = x, loop/closure variables, and everything crossing a file boundary other than by unique name.

Patch

I have a patch (extract_rust replacement + a small Rust pass in extract() for cross-file resolution) and 10 regression tests. Happy to open a PR. Summary of the behaviour:

  • IDs scoped by file path + mod path and tagged by kind; generics stripped so impl<T> Foo<T> attaches to Foo; implements edges (also cross-file); trait methods as nodes; nodes for mod/type/const/static/macro; use-trees flattened.
  • Calls resolved with light receiver-type inference (parameters, typed/constructor let, struct field types, Self, fn/method return types with Result/Option/Box/Arc unwrapped, if let Some(x), generic bounds, for/closure element types over Vec/slice/array). When a call can reach several impls, an edge goes to each candidate with INFERRED confidence. Calls on external types (Vec::new, u64::from, ...) and enum-variant/tuple-struct constructors produce no edge. Typed calls whose type lives in another file are resolved in a cross-file pass.

Measured on a real workspace

179 .rs files (a DICOM parser; calls counted over the whole graph, incl. cross-file), old extractor vs patched:

old patched
nodes (in-file extraction) 2658 5782+
calls edges 3544 4607 (2482 EXTRACTED, 2125 INFERRED)

The 134 old call edges that disappear were classified by source line: 68 Type::f() on external types, 43 enum-variant/tuple-struct constructors and chains on external types, 23 recv.f() on std types (len/is_empty/seek/read) — plus 4 validator.enter_* calls that used to point at the wrong free function and now point at the method. None was a Type::f() call on a type defined in the workspace.

Remaining limit

Where the type is only known to a real type checker (results of .map(..) / .collect(), macro-generated code), no edge is emitted rather than a guessed one.

Dominant language
Python
Stars
124k
Forks
11.9k
PR merge metrics
No merged PRs in 30d

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Graphify-Labs/graphify

All issues in Graphify-Labs/graphify

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.