Skip to content

Scratch/riel/rust owner names - #101

Merged
rikvanriel merged 5 commits into
facebookexperimental:mainfrom
rikvanriel:scratch/riel/rust-owner-names
Oct 14, 2026
Merged

rikvanriel merged 5 commits into
facebookexperimental:mainfrom
rikvanriel:scratch/riel/rust-owner-names

Conversation

@rikvanriel

Copy link
Copy Markdown
Contributor

Resolve and carry the owning entity of a definition.

This is a necessary component of being able to properly resolve a call chain through a code base with multiple functions of the same name, but in different classes.

Ten fn new in one Rust file are ten definitions, and the index keeps
whichever a length comparison prefers, so nine owners vanish. The name
column cannot tell them apart without renaming every row and breaking
every consumer that matches bare names, so the owner lives beside the
name instead: module::Type for an inherent method, module::<Type as
Trait<Args>> for a trait impl method, module::Trait for a trait
declaration, module for a free function, empty where the name stands
alone.

The column is part of the merge key, which grows to (name, file,
hash, guard, qualifier), and every pre-merge dedup keys on one shared
helper so the Rust-side key and the store-side key cannot drift apart.
Schema version 11; an index without the column is started again, and
each file is read again because the qualifier is extracted, not
derived.
A Rust method is stored under its bare name, so ten fn new in one
file are one row and a trait's bodiless declarations are no rows at
all: the function query only matches function_item, and
function_signature_item is a different node. The qualifier column has
nothing to carry until the extractor reads what owns each definition.

Walk up from each Rust definition through its enclosing items: inline
mod names nest under the file's module, an impl lends its type (or
<Type as Trait<Args)> for a trait impl), a trait lends its name, and
an enclosing function lends its own. The inherent type keeps its path
with generic arguments dropped, as the call site writes it; the trait
halves keep theirs, because From<u8> is not From<u16> and Pointer
for &T is not Pointer for &mut T. Identity shares nothing with
receiver typing, which sees through Arc to T: right for dispatch,
wrong here, where Arc::new and UniqueArc::new keyed on T are one row.

The within-file dedup keys on (name, guard, qualifier), so distinct
owners survive it, and the function query also matches bodiless trait
signatures through the existing declaration path.
Storing ten owners leaves the read side answering as if one name were
one definition. A spelled path (Arc::new) matches no row, because the
name column holds the bare name; one candidate per file keeps the
first new and reports the other nine as preprocessor arms; and every
surface that names a definition prints file and line with no owner, so
nine owners are indistinguishable where they are shown at all.

A name containing :: now resolves through its bare name and keeps only
the rows whose qualifier the path addresses: the qualifier itself, its
tail, or either half of a <Type as Trait> owner, since a caller
elsewhere cannot spell the stored module exactly. Grouping is per
file per owner, so coexisting definitions compete and only arms
collapse; the chain walks the arms under the chosen owner rather than
merging every owner's callees. Display renders qualifier::name, the
ambiguity note gains an owner variant that teaches the short ask, and
the [1 of N] markers, the per-definition lists, the MCP formats, and
the editor lookup all carry the owner. The editor falls back to the
bare item where the path addresses nothing, so C++ navigation reads
as before.
The residual-collision gate caught fourteen lost rows: zerocopy splits
Split into &T, &mut T, and Ptr inherent impls with the same methods,
and regulator and auxiliary do the same by state parameter. With
arguments dropped all three key on the bare type and two vanish; only
same-file duplicates cannot compile, so every within-file collision
is either a cfg pair (read in r7) or this bug.

Inherent impls now keep the full type text like trait impls do, and
qualified matching strips arguments for a query that spells none, so
Split::m still finds every instantiation while Split<&T>::m names one.
The matching split is top-level, so arguments containing :: do not
mislead it, and <Type as Trait> owners keep matching through either
half.
The C-resolution gate caught two flipped edges: complete (1,996 callers,
nearly all C) moved from kernel/sched/completion.c to a Rust method, and
verify moved from a C file to one. Both flips come from declaration rows
the old extractor never produced: a trait signature in a fifth file voted
its language into a majority and outvoted the definition files.

A trait signature declares a method, and no bare call reaches a method,
while a C prototype declares exactly what a bare call reaches. So a file
now votes only through a row a bare call could reach: a definition, or a
C declaration. The voter set is then what it was before owner extraction,
and the two flips resolve back to their C definitions.
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 14, 2026
@rikvanriel
rikvanriel merged commit 3a38a7b into facebookexperimental:main Oct 14, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant