From 30d3a1568c5813e80e3873f4f92519af8a08b7ad Mon Sep 17 00:00:00 2001 From: Stephen Adamson Date: Fri, 14 Nov 2025 19:28:56 +0000 Subject: [PATCH] patch rust --- mcp_codebase.py | 35 ++++++++++++++++++++++++++++++++++- tools/src/main.rs | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/mcp_codebase.py b/mcp_codebase.py index a9a5e96..c671bf2 100644 --- a/mcp_codebase.py +++ b/mcp_codebase.py @@ -1640,11 +1640,24 @@ def parse_rust_file_cached(filepath_str: str, file_hash: str) -> tuple: import_re = re.compile(r'^\s*use\s+([^;]+);', re.MULTILINE) # Fallback: Regex-based Rust parsing + # --- Extract Rust imports ---------------------------------------------- + imported_modules = import_re.findall(code) # ["std::fmt", "crate::pathfinding::PathNode", ...] + imports_clean = sorted({imp.split("::")[0] for imp in imported_modules}) + # ----------------------------------------------------------------------- + + # fallback: Regex-based Rust parsing logger.info(f" Falling back to regex parsing for {filepath}") regex_chunks = _parse_rust_with_regex(filepath, code, lines, import_re) chunks.extend(regex_chunks) logger.info(f" Regex found {len(regex_chunks)} chunks") + # Attach the imports list to every chunk before returning + for c in chunks: + md = dict(c["metadata"]) + md["imports"] = imports_clean + c["metadata"] = md + + except Exception as e: logger.warning(f"parse_rust_file failed {filepath}: {e}") @@ -2234,6 +2247,26 @@ def build_indexes(): param_type not in ['i32', 'i64', 'f32', 'f64', 'bool', 'String', 'str']): for type_id, type_data in graph.find_nodes(name=param_type): graph.add_edge(node_id, type_id, "uses_parameter") + elif node_type == "impl": + # ---------- IMPL NODES ---------- + node_id = f"{lang}::{node_type}::{fpath}::{name}" + created_nodes[node_id] = base_attrs.copy() + created_nodes[node_id]["type"] = node_type + + graph.add_node(node_id, **base_attrs, type=node_type) + + # Try to find the target struct/trait that this impl implements + target_name = None + if "implements_traits" in m: + target_name = m["implements_traits"][0] # assume first trait + elif "implements" in m: + target_name = m["implements"][0] + + if target_name: + target_node_id = f"{lang}::struct::{fpath}::{target_name}" + if target_node_id not in graph.graph: + graph.add_node(target_node_id, name=target_name, file=fpath, lang=lang, type="struct") + graph.add_edge(node_id, target_node_id, "implements") else: # Regular function graph.add_node(node_id, **base_attrs, type=node_type, @@ -3573,7 +3606,7 @@ def _get_result_graph_context(meta: dict, graph: Optional[LocalGraph] = None) -> # Build node ID node_id = None if entity_name and file_path and language: - if entity_type in ['class', 'struct', 'interface', 'enum']: + if entity_type in ['class', 'struct', 'interface', 'enum', 'impl']: node_id = f"{language}::{entity_type}::{file_path}::{entity_name}" elif entity_type in ['method', 'function', 'constructor']: class_name = meta.get('class') diff --git a/tools/src/main.rs b/tools/src/main.rs index 9187d93..95cbd59 100644 --- a/tools/src/main.rs +++ b/tools/src/main.rs @@ -23,6 +23,7 @@ struct RustDecl { methods: Vec, return_type: Option, parameters: Vec, + docstring: Option, } fn main() { @@ -292,8 +293,18 @@ fn parse_impl(item_impl: &ItemImpl) -> Option { .into_iter() .collect::>(); + // Prefer the first trait name (if any) as the identifier; otherwise fall back to the target type + let name = if !implements.is_empty() { + implements[0].clone() + } else { + target + }; + + // Extract any docstring attached to the impl block + let docstring = extract_docstring(&item_impl.attrs); + Some(RustDecl { - name: format!("impl_{}", target), + name, type_: "impl".to_string(), start_line, end_line, @@ -311,9 +322,39 @@ fn parse_impl(item_impl: &ItemImpl) -> Option { methods, return_type: None, parameters: Vec::new(), + docstring, }) } +/// Pull a concatenated doc‑string from a list of attributes. +/// Handles both `///` comments (converted to `#[doc = "..."]` by syn) +/// and explicit `#[doc = "..."]` attributes. +fn extract_docstring(attrs: &[syn::Attribute]) -> Option { + let mut docs = Vec::new(); + + for attr in attrs { + if attr.path.is_ident("doc") { + // #[doc = "…"] + if let Ok(meta) = attr.parse_meta() { + if let syn::Meta::NameValue(nv) = meta { + if let syn::Lit::Str(lit) = nv.lit { + docs.push(lit.value()); + } + } + } + } else if attr.path.is_ident("doc") { + // Just in case `#[doc]` appears without a value (unlikely but harmless) + continue; + } + } + + if docs.is_empty() { + None + } else { + Some(docs.join("\n")) + } +} + fn parse_mod(item_mod: &ItemMod) -> Option { let (start_line, end_line) = span_start_end(item_mod);