Summary
handle_trait_item in crates/codegraph-core/src/extractors/rust_lang.rs calls compute_all_metrics(&child, source, "rust") unconditionally for every trait member, regardless of whether child.kind() is function_signature_item (a required trait method with no default implementation, no body) or function_item (a default-implemented trait method, has a body):
if child.kind() != "function_signature_item" && child.kind() != "function_item" {
continue;
}
if let Some(meth_name) = child.child_by_field_name("name") {
symbols.definitions.push(Definition {
name: format!("{}.{}", trait_name, node_text(&meth_name, source)),
kind: "method".to_string(),
line: start_line(&child),
end_line: Some(end_line(&child)),
decorators: None,
complexity: compute_all_metrics(&child, source, "rust"),
cfg: build_function_cfg(&child, "rust", source),
children: None,
...
});
}
compute_all_metrics doesn't check whether the passed node's kind is actually a member of the language's own function_nodes rule set — it just walks whatever node it's given, counting branch/nesting node types among its descendants. For a function_signature_item (no body, just fn name(&self, ...) -> T;), this produces a trivial but non-null ComplexityMetrics { cognitive: 0, cyclomatic: 1, ... }, because a bodyless signature has no descendants that look like branches.
Meanwhile, the WASM/TS side's COMPLEXITY_RULES for Rust (src/ast-analysis/rules/rust.ts) deliberately excludes function_signature_item from functionNodes:
functionNodes: new Set(['function_item', 'closure_expression']),
So the WASM complexity visitor never even attempts to compute complexity for a function_signature_item — it produces no complexity entry at all for that definition.
Reproduction
trait Repo {
fn save(&mut self, id: &str, value: i32) -> bool; // required, no body
fn find(&self, id: &str) -> Option<i32> { // default impl, has body
if id.is_empty() { return None; }
for i in 0..3 { if i == 1 { return Some(i); } }
None
}
}
$ codegraph complexity --file repo.rs -T --json --engine wasm
functions: 1 [ Repo.find ]
$ codegraph complexity --file repo.rs -T --json --engine native
functions: 2 [ Repo.find, Repo.save ] ← Repo.save has cyclomatic: 1, cognitive: 0
Per repo convention (CLAUDE.md): "Both engines must produce identical results. If they diverge, the less-accurate engine has a bug." Here WASM is correct (a signature-only trait method has no complexity to report); native is wrong (fabricates a trivial-but-meaningless complexity entry for a function with no body).
Suggested fix
handle_trait_item should skip calling compute_all_metrics/build_function_cfg for function_signature_item children and push complexity: None, cfg: None instead — mirroring how csharp.rs's handle_interface_decl already handles this same situation for C# interface methods (explicit None with a comment: "Interface method declarations have no body — skip CFG and complexity to mirror the WASM extractor").
Discovery context
Found while fixing #1922 (WASM complexity engine skips entire file when every function has a dotted name). That fix added a Definition.bodyless signal (set via child.child_by_field_name("body").is_none()) to both engines so the file-level "does this file need complexity" gate no longer misclassifies real bodied dotted-name functions as signature stubs. While auditing every native extractor site that mirrors a WASM interface/trait stub extractor to set bodyless correctly, rust_lang.rs's trait handling was the only one where the existing complexity/cfg values (not just the new bodyless flag) already diverged from WASM before that fix — an independent, pre-existing bug, not something introduced by #1922's change. Not fixed there to keep that PR scoped to the file-level gate.
Summary
handle_trait_itemincrates/codegraph-core/src/extractors/rust_lang.rscallscompute_all_metrics(&child, source, "rust")unconditionally for every trait member, regardless of whetherchild.kind()isfunction_signature_item(a required trait method with no default implementation, no body) orfunction_item(a default-implemented trait method, has a body):compute_all_metricsdoesn't check whether the passed node's kind is actually a member of the language's ownfunction_nodesrule set — it just walks whatever node it's given, counting branch/nesting node types among its descendants. For afunction_signature_item(no body, justfn name(&self, ...) -> T;), this produces a trivial but non-nullComplexityMetrics { cognitive: 0, cyclomatic: 1, ... }, because a bodyless signature has no descendants that look like branches.Meanwhile, the WASM/TS side's
COMPLEXITY_RULESfor Rust (src/ast-analysis/rules/rust.ts) deliberately excludesfunction_signature_itemfromfunctionNodes:So the WASM complexity visitor never even attempts to compute complexity for a
function_signature_item— it produces no complexity entry at all for that definition.Reproduction
Per repo convention (CLAUDE.md): "Both engines must produce identical results. If they diverge, the less-accurate engine has a bug." Here WASM is correct (a signature-only trait method has no complexity to report); native is wrong (fabricates a trivial-but-meaningless complexity entry for a function with no body).
Suggested fix
handle_trait_itemshould skip callingcompute_all_metrics/build_function_cfgforfunction_signature_itemchildren and pushcomplexity: None, cfg: Noneinstead — mirroring howcsharp.rs'shandle_interface_declalready handles this same situation for C# interface methods (explicitNonewith a comment: "Interface method declarations have no body — skip CFG and complexity to mirror the WASM extractor").Discovery context
Found while fixing #1922 (WASM complexity engine skips entire file when every function has a dotted name). That fix added a
Definition.bodylesssignal (set viachild.child_by_field_name("body").is_none()) to both engines so the file-level "does this file need complexity" gate no longer misclassifies real bodied dotted-name functions as signature stubs. While auditing every native extractor site that mirrors a WASM interface/trait stub extractor to setbodylesscorrectly,rust_lang.rs's trait handling was the only one where the existingcomplexity/cfgvalues (not just the newbodylessflag) already diverged from WASM before that fix — an independent, pre-existing bug, not something introduced by #1922's change. Not fixed there to keep that PR scoped to the file-level gate.