mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-25 03:49:59 +08:00
fix(rust): a turbofish call reaches the function the turbofish wraps (#2720)
A Rust call written with a turbofish (`s.get::<u32>()`, `helper::<u8>()`, `Pool::<u8>::new()`) kept its type arguments in the callee's name (`s.get::<u32>`), which no resolution strategy reads, so every such call was left unresolved. Both engines now name the function the turbofish wraps and drop a path's type arguments (`Pool::<u8>::new` is `Pool::new`), so the call resolves as it would without the turbofish: on a typed receiver, on `self` or a field of `self`, and through an associated-function chain. The kernel and wasm extractors emit the same bytes; the parity fixture covers nested type arguments and a `->` inside them. A/B against main with fresh indexes, sites matched by position: clap 197 calls linked, dioxus 251 linked and 1 removed, axum 69, tokio 143, serde 48, ripgrep 3; gin and express are byte-identical. About 680 of the new links are right: typed method calls (clap's `matches.get_one::<String>()` to `ArgMatches::get_one`, dioxus' `self.query.new_query::<T>()` to `QueryEngine::new_query`) and generic free functions (tokio's `channel::<T>()`, dioxus' `consume_context::<T>()`, serde's `assert::<T>()`). About 22 are weak guesses at confidence 0.5 or below, where an instance chain's method becomes a bare name, as it is for the same call without a turbofish (dioxus' `.collect::<Vec<_>>()` to `ServerFunction::collect`; main already makes 23 of those for `.collect()`). The one removed link was wrong on main: `serde_json::from_str::<T>(...).context(...)` is no longer guessed as `DiffState::context`. The 54 sites reported as retargeted are positions a chain's inner and outer calls share, where an edge was added beside the existing one. Full suite on the tested tree: 8,037 passed, 0 failed. Follow-up 1 of the Rust receiver series (PR 2719's open list). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7324faf69f
commit
bd62d9c061
@@ -882,6 +882,11 @@ impl<'t> Walker<'t> {
|
||||
let func = node
|
||||
.child_by_field_name("function")
|
||||
.or_else(|| node.named_child(0));
|
||||
// A turbofish — `s.get::<u32>()`, `helper::<u8>()`,
|
||||
// `Store::with_capacity::<u8>(1)` — calls the function it wraps: its
|
||||
// type arguments are no part of the callee's name. Mirrors
|
||||
// TreeSitterExtractor.extractCall.
|
||||
let func = func.map(unwrap_generic_function);
|
||||
let mut callee_name = String::new();
|
||||
|
||||
if let Some(func) = func {
|
||||
@@ -920,7 +925,7 @@ impl<'t> Walker<'t> {
|
||||
// function chain (`Foo::new().bar()`, inner
|
||||
// callee a scoped_identifier). Instance chains
|
||||
// keep the bare method name.
|
||||
let inner_fn = r.child_by_field_name("function");
|
||||
let inner_fn = r.child_by_field_name("function").map(unwrap_generic_function);
|
||||
let reencode =
|
||||
inner_fn.map(|f| f.kind() == "scoped_identifier").unwrap_or(false);
|
||||
if reencode {
|
||||
@@ -930,6 +935,7 @@ impl<'t> Walker<'t> {
|
||||
.chars()
|
||||
.filter(|c| !c.is_whitespace())
|
||||
.collect();
|
||||
let inner = strip_turbofish(&inner);
|
||||
callee_name = format!("{inner}().{method_name}");
|
||||
} else {
|
||||
callee_name = method_name.to_string();
|
||||
@@ -974,10 +980,11 @@ impl<'t> Walker<'t> {
|
||||
}
|
||||
}
|
||||
} else if matches!(func.kind(), "scoped_identifier" | "scoped_call_expression") {
|
||||
callee_name = self.text(func).to_string();
|
||||
// A path's turbofish is no part of the name either
|
||||
// (`Vec::<u8>::with_capacity` → `Vec::with_capacity`).
|
||||
callee_name = strip_turbofish(self.text(func));
|
||||
} else {
|
||||
// identifier; generic_function keeps the raw turbofish text
|
||||
// (`helper::<T>` — unresolvable downstream, preserved).
|
||||
// identifier (a turbofish was unwrapped above).
|
||||
callee_name = self.text(func).to_string();
|
||||
}
|
||||
}
|
||||
@@ -1601,6 +1608,52 @@ fn is_stoplisted(name: &str) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
/// The function a turbofish wraps: `s.get::<u32>` → `s.get`,
|
||||
/// `helper::<u8>` → `helper`; any other callee as is.
|
||||
fn unwrap_generic_function(func: Node<'_>) -> Node<'_> {
|
||||
if func.kind() == "generic_function" {
|
||||
func.child_by_field_name("function").unwrap_or(func)
|
||||
} else {
|
||||
func
|
||||
}
|
||||
}
|
||||
|
||||
/// A callee path without its turbofish type arguments: `Vec::<u8>::new` →
|
||||
/// `Vec::new`. The brackets nest (`::<Vec<u8>>`), and the `>` of a `->` is
|
||||
/// no closing one (`::<fn() -> u8>`). Mirrors stripRustTurbofish in
|
||||
/// TreeSitterExtractor: both emit the same bytes.
|
||||
fn strip_turbofish(text: &str) -> String {
|
||||
if !text.contains("::<") {
|
||||
return text.to_string();
|
||||
}
|
||||
let chars: Vec<char> = text.chars().collect();
|
||||
let mut out = String::with_capacity(text.len());
|
||||
let mut i = 0;
|
||||
while i < chars.len() {
|
||||
if chars[i] == ':' && i + 2 < chars.len() && chars[i + 1] == ':' && chars[i + 2] == '<' {
|
||||
let mut depth = 0i32;
|
||||
let mut j = i + 2;
|
||||
while j < chars.len() {
|
||||
let c = chars[j];
|
||||
if c == '<' {
|
||||
depth += 1;
|
||||
} else if c == '>' && chars[j - 1] != '-' {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
i = j + 1;
|
||||
continue;
|
||||
}
|
||||
out.push(chars[i]);
|
||||
i += 1;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// LITERAL_RECEIVER_TYPES (shared table).
|
||||
fn is_literal_receiver(kind: &str) -> bool {
|
||||
matches!(
|
||||
|
||||
Reference in New Issue
Block a user