An external trait method call
The preceding examples enter the solver with a trait already named. A method call begins one step earlier: method-name lookup must discover candidate traits before the solver can answer a fixed-trait question.
This chapter follows the first implemented external-method vertical slice:
#![allow(unused)] fn main() { #[derive(Clone)] struct Db { shared: bool, } struct DbDropGuard { db: Db, } impl DbDropGuard { fn db(&self) -> Db { self.db.clone() } } }
The source expression is a field access followed by a method call. Its completed typed form is conceptually:
Call <Db as Clone>::clone(
RefShared[Dummy](Field DbDropGuard::db(Deref(Local self)))
)
1. Discover traits before proving one
Method resolution clones the expression scope's resolver and asks it for traits which can participate in method lookup:
example_traits_in_method_scope
The current slice sees traits defined directly in the module, traits introduced
by resolved explicit imports or external glob imports, and traits from the
crate's actual edition prelude. A local glob remains incomplete until its
reexport and macro-provider edges can be enumerated recursively. The edition
is retained on the crate and every module so root macro expansion and ordinary
checking make the same choice. The returned
complete bit is as important as the list. Unresolved expansion, active
attributes, or an unresolved import makes the source incomplete, so absence
from the returned list cannot justify NotFound or selecting a sole visible
candidate.
For every discovered trait, method lookup first requests its associated-item list and filters functions by the source name:
example_discover_trait_methods
This is name discovery, not trait solving. In particular, the solver is never
asked an existential question such as “which trait gives Db a method named
clone?”
2. Cross the external metadata boundary narrowly
Clone and Clone::clone belong to an external crate. TcxDb returns owned raw
metadata, and tracked lowering queries convert that data into Sage symbols,
types, and binders. Associated-item enumeration is separate from signature
loading:
example_external_trait_items
Only after a function name matches does lookup request the defining trait and function signatures. The function query is deliberately a separate incremental boundary:
example_external_fn_signature
This means an unrelated method body—or every signature in every prelude
trait—does not automatically become a dependency of DbDropGuard::db.
3. Ask the fixed-trait question
For the matching Clone::clone item, the current implementation accepts only
an eligible trait whose sole type parameter is Self. It constructs the
specific proposition Db: Clone and classifies its solver result:
example_classify_trait_candidate
The classification entry point canonicalizes and proves that goal through the ordinary trait solver:
example_classify_fixed_trait_goal
This first slice is intentionally read-only. A goal containing a caller
inference variable becomes Maybe, as does a conditional Yes with a
non-trivial residual. Only an unconditional Yes with no caller inference
variables and a trivial residual makes the method a definite candidate. The
local impl emitted by #[derive(Clone)] supplies that proof for Db.
4. Instantiate and select the method
Once the fixed-trait proof succeeds, lookup loads Clone::clone, substitutes
Db for the trait's Self, and records a statically dispatched call target:
example_instantiate_trait_method
Within the represented trait tier, selection accounts for incomplete information. Exactly one definite candidate is returned only when no competing or unsupported source—including an unhandled inherent provider—is unknown:
example_select_trait_method
This preserves the distinction between “no method exists” and “the represented subset cannot decide.” It also prevents trait enumeration order from choosing among multiple applicable methods.
5. Consume method syntax into completed IR
Expression checking applies the selected signature, checks ordinary arguments,
submits its parameter environment, and materializes &self as an explicit
shared reference. The result is ResolvedCall, not a retained method-name node:
example_elaborate_trait_method_call
The reference carries Lifetime::Dummy, matching Sage's current lifetime
boundary. The completed body now records the selected function, the static
Db: Clone dispatch, the resolved DbDropGuard::db field, the dereference of
the method's own &self, and the borrow required by Clone::clone.
6. Prioritize a represented external inherent method
The same name-keyed external boundary is now also a selection boundary for a rigid external ADT. A complete empty result permits trait fallback. A nonempty result filters visibility and requires exactly one visible function before its signature is loaded:
example_select_external_inherent_method
The selected signature retains how many leading binder parameters belong to the owning impl. Method checking creates fresh variables for all represented owner and method type generics, matches the owner self type against the receiver, and matches every ordinary argument inside one child inference version. The function parameter environment is staged beside that child and is published only after commit:
example_instantiate_external_inherent_method
For Option<Frame>::ok_or(ParseError::EndOfStream), receiver matching binds
the owner T to Frame, while argument matching independently binds the
method E to ParseError. The selected call uses direct dispatch; it does not
ask trait proof to return an impl.
7. Retain the semantic call in shared IR
Completed calls preserve these choices instead of requiring a downstream consumer to reconstruct them. The shared dispatch variant distinguishes a direct inherent call from static trait dispatch:
example_call_dispatch_schema
The call itself retains owner and method substitutions separately:
example_call_substitution_schema
Sage emits those fields from ResolvedCallTarget; the rustc oracle projects
its selected associated item and node substitutions into the same schema
without reading Sage output. The isolated Parse::next test checks the
semantic fields and then requires both independent trees to equal one exact
snapshot:
example_exact_parse_next_snapshot
Current boundary
This is a deliberately narrow vertical slice, not the complete method algorithm specified by the Method Resolution RFD. Local-glob provider enumeration, explicit-bound provider enumeration, local and builtin inherent-method selection, generic trait methods, receiver autoderef, conditional candidates, and retained lookup obligations remain planned. The current function does not resolve local or builtin inherent methods. It conservatively reports uncertainty when a local inherent impl could provide a competitor. For a rigid external ADT, it can audit or select from complete same-name receiver-bearing metadata; other external or builtin receiver forms remain unenumerated. Every unrepresented candidate source stays uncertain rather than becoming a false unique result.