Skip to content

fix: Rust crate::/super::/self:: use paths never resolve, so trait impls link to phantom externals and lose all OVERRIDES edges #1007

Closed

Description

@vitali87

Summary

Rust use crate::... (and super::/self::) paths are stored raw in the import mapping and never rewritten to project qualified names. When an impl Trait for Type block names a trait imported this way, the deferred inheritance resolver sees a qn that does not start with the project prefix, declares it external, and emits IMPLEMENTS onto a phantom ExternalModule node such as crate.flags.Flag. Because the trait parent never resolves first party, the method override pass emits no OVERRIDES edges from the impl methods to the trait methods.

Dead-code impact is severe: calls through &dyn Trait receivers resolve (correctly) to the trait's method declarations, but without OVERRIDES edges the liveness walk cannot expand from a live trait method to its implementations. On ripgrep this single defect accounts for 938 of 1811 dead-code candidates (926 impl methods in crates/core/flags/defs.rs plus 12 trait members): every one of the ~200 CLI flag structs implements Flag and is invoked solely through pub(super) const FLAGS: &[&dyn Flag].

Minimal repro

Three files, standard src layout (the ripgrep crates/core layout without a src directory fails identically):

src/main.rs

mod flags;

fn main() {
    for flag in crate::flags::defs::FLAGS {
        println!("{}", flag.name_long());
    }
}

src/flags.rs

pub(crate) mod defs;

pub trait Flag {
    fn name_long(&self) -> &'static str;
}

src/flags/defs.rs

use crate::flags::Flag;

pub(crate) struct AfterContext;

impl Flag for AfterContext {
    fn name_long(&self) -> &'static str {
        "after-context"
    }
}

pub(crate) const FLAGS: &[&dyn Flag] = &[&AfterContext];

Actual graph:

proj.src.flags.defs.AfterContext IMPLEMENTS crate.flags.Flag      <- phantom external
proj.src.main.main CALLS proj.src.flags.Flag.name_long
(no OVERRIDES edges at all)

Expected:

proj.src.flags.defs.AfterContext IMPLEMENTS proj.src.flags.Flag
proj.src.flags.defs.AfterContext.name_long OVERRIDES proj.src.flags.Flag.name_long

With the OVERRIDES edge present the existing dead-code override expansion (override_rev) makes AfterContext.name_long live as soon as Flag.name_long is called, which is exactly the semantics of dynamic dispatch through a trait object.

Root cause

  1. _parse_rust_use_declaration stores extract_use_imports output verbatim, so import_mapping[module]["Flag"] = "crate::flags::Flag". Nothing downstream rewrites the crate:: prefix, so resolve_class_name hands the raw path to _resolve_deferred_parent_qn, which externalises it (crate.flags.Flag).
  2. The one place that does rewrite crate paths, _resolve_rust_import_path, only serves the module-level IMPORTS edge, and its crate-root detection assumes a literal src directory segment. ripgrep's core crate lives at crates/core/main.rs with no src directory, so even that path falls back to the project root and produces a wrong qn.

Same syntax note: crate:: is always the crate root, not the current module; super:: is the parent module; self:: is the current module. The crate root is the directory holding the crate entry point (lib.rs/main.rs), which the standard layout places under src/ but Cargo allows anywhere via [lib] path/[[bin]] path.

Verified on ripgrep at master; graph inspected in Memgraph (IMPLEMENTS count 235, all pointing at crate.* phantoms for the flags family; 16 OVERRIDES edges in the whole project, none into Flag).

Activity

  1. vitali87 commented on Aug 2, 2026

    @vitali87
    OwnerAuthor

    Fix up as PR #1027: use paths beginning with crate::/super::/self:: now resolve to project qualified names at parse time, restoring first-party IMPLEMENTS targets and OVERRIDES edges (16 to 816 on ripgrep). The PR closes this issue on merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions