mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-05 08:23:53 +08:00
fix(rust-lsp): a generic impl's self type drops its type arguments
The extractor fix above names a generic impl's methods `Gen.in_generic` on both the def and the call-scope side. The Rust LSP still built its self type from the impl's raw text, `Gen<T>`. That broke three things: - its caller QN (`Gen<T>.in_generic`) no longer joined the pipeline's call scope, so every LSP row inside a generic or lifetime impl was dropped for a registry guess. On 9 Rust repos that turned 1,927 lsp_* edges into suffix_match/unique_name; - `self.` and `Self::` dispatch never found the impl's own methods; `Self::assoc_g()` had no edge at all, on main as well; - the return-type harvest never matched those methods, so chained calls on them stayed untyped. Strip the generic arguments from the impl's self type in both places. Text that starts with `<` (a qualified path) is left alone. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
@@ -306,6 +306,22 @@ static void rust_strip_turbofish(char *path) {
|
||||
*write = '\0';
|
||||
}
|
||||
|
||||
/* In place: `Gen<T>` -> `Gen`, `foo::Bar<'a, T>` -> `foo::Bar` (#763). An
|
||||
* impl's self type keeps its generic arguments in the source text, but every
|
||||
* def-side QN (a method's receiver, the extractor's call scope) strips them.
|
||||
* Unstripped, the LSP's caller QN, `self`/`Self` dispatch and the return-type
|
||||
* harvest never matched a generic impl's methods. Text starting with `<` (a
|
||||
* qualified path) is left alone. Safe on NULL. */
|
||||
static void rust_strip_impl_type_args(char *type_text) {
|
||||
if (!type_text) {
|
||||
return;
|
||||
}
|
||||
char *lt = strchr(type_text, '<');
|
||||
if (lt && lt > type_text) {
|
||||
*lt = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/* Look up a `use` alias and return its fully-qualified module path,
|
||||
* or NULL if absent. The returned pointer aliases into the use map. */
|
||||
static const char *rust_resolve_use(RustLSPContext *ctx, const char *local_name) {
|
||||
@@ -5245,6 +5261,7 @@ static void rust_process_impl(RustLSPContext *ctx, TSNode impl_node) {
|
||||
char *type_text = rust_node_text(ctx, type_node);
|
||||
if (!type_text)
|
||||
return;
|
||||
rust_strip_impl_type_args(type_text);
|
||||
|
||||
/* Detect blanket impl: `impl<T: Trait> ForeignTrait for T { ... }`
|
||||
* where type_text is a name that appears in the impl's type
|
||||
@@ -5999,6 +6016,7 @@ void cbm_rust_build_local_registry(CBMArena *arena, CBMTypeRegistry *reg, CBMFil
|
||||
if (ts_node_is_null(type_node) || ts_node_is_null(body))
|
||||
continue;
|
||||
char *type_name = cbm_node_text(arena, type_node, source);
|
||||
rust_strip_impl_type_args(type_name);
|
||||
if (!type_name || !type_name[0])
|
||||
continue;
|
||||
const char *type_qn = cbm_arena_sprintf(arena, "%s.%s", module_qn, type_name);
|
||||
|
||||
+26
-11
@@ -8879,17 +8879,21 @@ static int rust_generic_impl_attribution_case(int pad_files) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_temp_file(tmp, "Cargo.toml", "[package]\nname = \"fx\"\nversion = \"0.1.0\"\n");
|
||||
write_temp_file(tmp, "src/lib.rs",
|
||||
"pub struct Gen<T> { v: T }\n"
|
||||
"impl<T> Gen<T> {\n"
|
||||
" pub fn in_generic(&self) -> u32 { helper_a() }\n"
|
||||
"}\n"
|
||||
"pub struct Life<'a> { s: &'a str }\n"
|
||||
"impl<'a> Life<'a> {\n"
|
||||
" pub fn in_lifetime(&self) -> u32 { helper_b() }\n"
|
||||
"}\n"
|
||||
"pub fn helper_a() -> u32 { 1 }\n"
|
||||
"pub fn helper_b() -> u32 { 2 }\n");
|
||||
write_temp_file(
|
||||
tmp, "src/lib.rs",
|
||||
"pub struct Gen<T> { v: T }\n"
|
||||
"impl<T> Gen<T> {\n"
|
||||
" pub fn in_generic(&self) -> u32 { helper_a() + self.other_g() + Self::assoc_g() }\n"
|
||||
" pub fn other_g(&self) -> u32 { 3 }\n"
|
||||
" pub fn assoc_g() -> u32 { 4 }\n"
|
||||
"}\n"
|
||||
"pub struct Life<'a> { s: &'a str }\n"
|
||||
"impl<'a> Life<'a> {\n"
|
||||
" pub fn in_lifetime(&self) -> u32 { helper_b() + self.other_l() }\n"
|
||||
" pub fn other_l(&self) -> u32 { 5 }\n"
|
||||
"}\n"
|
||||
"pub fn helper_a() -> u32 { 1 }\n"
|
||||
"pub fn helper_b() -> u32 { 2 }\n");
|
||||
for (int i = 0; i < pad_files; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
@@ -8918,6 +8922,17 @@ static int rust_generic_impl_attribution_case(int pad_files) {
|
||||
/* ... and not from the file. */
|
||||
ASSERT_FALSE(cross_file_call_exists(s, project, "lib.rs", "helper_a"));
|
||||
ASSERT_FALSE(cross_file_call_exists(s, project, "lib.rs", "helper_b"));
|
||||
/* The LSP side names the impl's self type without its arguments too: its
|
||||
* rows join these callers (lsp_*, not a registry fallback), and `self.` /
|
||||
* `Self::` dispatch inside a generic impl resolves (Self::assoc_g had no
|
||||
* edge at all). */
|
||||
ASSERT_TRUE(
|
||||
cross_file_call_has_strategy(s, project, "in_generic", "helper_a", "\"strategy\":\"lsp_"));
|
||||
ASSERT_TRUE(
|
||||
cross_file_call_has_strategy(s, project, "in_generic", "other_g", "\"strategy\":\"lsp_"));
|
||||
ASSERT_TRUE(cross_file_call_exists(s, project, "in_generic", "assoc_g"));
|
||||
ASSERT_TRUE(
|
||||
cross_file_call_has_strategy(s, project, "in_lifetime", "other_l", "\"strategy\":\"lsp_"));
|
||||
|
||||
cbm_store_close(s);
|
||||
cbm_pipeline_free(p);
|
||||
|
||||
Reference in New Issue
Block a user