Skip to content

fix(native): align edge builder kind filters with JS parity#541

Merged
carlos-alm merged 4 commits intomainfrom
fix/native-hierarchy-edge-parity
Mar 20, 2026
Merged

fix(native): align edge builder kind filters with JS parity#541
carlos-alm merged 4 commits intomainfrom
fix/native-hierarchy-edge-parity

Conversation

@carlos-alm
Copy link
Contributor

Summary

  • Fix: Native Rust edge builder only matched kind == "class" when looking up source nodes and targets for extends/implements edges, causing all non-class hierarchy relationships to be silently dropped
  • Impact: 9 implements edges missing in v3.3.0 dogfood report (all impl SymbolExtractor for XxxExtractor from Rust source files)
  • Aligns three kind filter sets with the JS-side constants in build-edges.js:
    • Source kinds: classclass, struct, record, enum
    • Extends targets: classclass, struct, trait, record
    • Implements targets: interface, classinterface, class, trait

Root cause

The Rust build_call_edges() in edge_builder.rs hardcoded n.kind == "class" for source lookups (line 342) and extends targets (line 348), and omitted "trait" from implements targets (line 365). The JS fallback (build-edges.js:403-405) correctly uses HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, and IMPLEMENTS_TARGET_KINDS which include struct, record, enum, and trait. Since Rust extractors emit struct (not class) for Rust structs, the source lookup always failed and both extends and implements edges were silently dropped.

Test plan

  • 1998 JS tests pass (105 test files)
  • Verified native extraction produces correct ClassRelation ({name: "JsExtractor", implements: "SymbolExtractor"})
  • Verified native buildCallEdges() produces correct implements edge when given proper node kinds
  • Confirmed WASM build produces 15 implements edges vs native's 6 (9 missing = all Rust impl Trait for Struct)
  • Rebuild native binary and verify implements edge count matches WASM

The Rust edge builder only matched `kind == "class"` when looking up
source nodes and targets for extends/implements edges. This caused all
`impl Trait for Struct` relationships (and any non-class hierarchy) to
be silently dropped — producing 0 implements edges for Rust sources
while WASM correctly found 9.

Align the three kind filter sets with the JS-side constants:
- Source: class, struct, record, enum (was: class only)
- Extends targets: class, struct, trait, record (was: class only)
- Implements targets: interface, class, trait (was: interface, class)

Fixes #530 (partial — implements parity gap)

Impact: 1 functions changed, 0 affected
@claude
Copy link

claude bot commented Mar 19, 2026

Claude finished @carlos-alm's task —— View job


PR Review: fix(native): align edge builder kind filters with JS parity

Root Cause Analysis

Problem Being Solved: Native Rust edge builder was using hardcoded kind == "class" filters instead of comprehensive kind sets, causing missing hierarchy edges (especially impl Trait for Struct relationships in Rust code)

Why This Approach: The fix correctly aligns the native Rust implementation with the established JS fallback constants (HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, IMPLEMENTS_TARGET_KINDS), ensuring behavioral parity between engines

Risk Assessment: Low risk - this is purely fixing a bug where native was more restrictive than JS, no existing behavior is broken

Code Quality Assessment

✅ Excellent execution:

  • Perfect alignment: The changes at lines 343, 351, and 369 in edge_builder.rs exactly match the JS constants in build-edges.js:403-405
  • Surgical precision: Only the 3 problematic filter conditions were changed, no unnecessary modifications
  • Clear impact: Addresses the specific issue of 9 missing implements edges identified in dogfooding

✅ Technical correctness:

  • Source kinds: classclass || struct || record || enum (line 343)
  • Extends targets: classclass || struct || trait || record (line 351)
  • Implements targets: interface || classinterface || class || trait (line 369)

✅ Well-documented:

  • Commit message clearly explains the bug and impact
  • PR description provides excellent root cause analysis
  • References specific missing edges from dogfood analysis

Test Coverage Concerns

⚠️ Minor gap: Test plan shows "Rebuild native binary and verify implements edge count matches WASM" is still pending. However:

  • JS tests passing (1998 tests) provides confidence in logic
  • Verified correct ClassRelation extraction
  • Confirmed buildCallEdges() produces expected edges with proper node kinds
  • The fix is straightforward alignment with proven JS logic

Backlog Compliance

  • Zero-dep: ✅ No new dependencies, pure bug fix
  • Foundation-aligned: ✅ Maintains local-first architecture, improves accuracy
  • Problem-fit: 5/5 - Directly addresses AI agent accuracy by preventing missing dependency relationships
  • Breaking: No - Purely additive, only surfaces previously missing edges
  • Tier: 1 (zero-dep ✅ + foundation-aligned ✅)

Critical Concerns

None identified. This is a well-executed bug fix.

Final Recommendation

  • Rating: ⭐⭐⭐⭐☆ (4/5)
  • Action: APPROVE
  • Reasoning: Clean bug fix with perfect JS parity alignment, clear impact analysis, and comprehensive testing. The only minor concern is the pending native binary verification, but the logic is sound and well-tested on the JS side.

Suggestion for completion: Once the native binary is rebuilt and the implements edge count verification passes, this will be a perfect 5-star PR.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 19, 2026

Greptile Summary

This PR completes a two-step fix for native/WASM parity in hierarchy edge resolution. The actual bug fix (expanding the kind == "class" guards to include struct, record, enum, and trait) was landed in a prior commit; this follow-up refactoring extracts the three inline kind-filter sets into named module-level constants (HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, IMPLEMENTS_TARGET_KINDS) that mirror the equivalent JS constants in build-edges.js.

Key observations:

  • All three Rust constants match their JS counterparts exactly (values and membership are identical).
  • The refactoring is purely cosmetic — no runtime behaviour changes.
  • The comment block above the constants explicitly documents the JS mirror and the reason for keeping them co-located, which is a good guard against future drift.

Confidence Score: 5/5

  • This PR is safe to merge — it is a pure refactoring with no functional changes.
  • The three Rust constants introduced exactly replicate the inline string values already present after the prior bug-fix commit, and they match the JS constants verbatim. No logic paths changed; only the source of the filter strings moved to named constants. No new edge cases are introduced.
  • No files require special attention.

Important Files Changed

Filename Overview
crates/codegraph-core/src/edge_builder.rs Extracts three inline kind-filter string sets into named module-level constants (HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, IMPLEMENTS_TARGET_KINDS) that exactly mirror the JS-side constants in build-edges.js. No functional change — the constant values are identical to the inline strings already present after the prior bug-fix commit.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[ClassInfo entry] --> B{source_row found?\nHIERARCHY_SOURCE_KINDS\nclass · struct · record · enum}
    B -- No --> Z[skip]
    B -- Yes --> C{cls.extends?}
    C -- Yes --> D[Filter target nodes\nEXTENDS_TARGET_KINDS\nclass · struct · trait · record]
    D --> E[emit 'extends' edge\nconfidence 1.0]
    C -- No --> F{cls.implements?}
    E --> F
    F -- Yes --> G[Filter target nodes\nIMPLEMENTS_TARGET_KINDS\ninterface · trait · class]
    G --> H[emit 'implements' edge\nconfidence 1.0]
    F -- No --> Z
Loading

Last reviewed commit: "fix: resolve merge c..."

Comment on lines +342 to +344
.and_then(|v| v.iter().find(|n| {
n.kind == "class" || n.kind == "struct" || n.kind == "record" || n.kind == "enum"
}));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Consider extracting kind sets as named constants

The three inline kind-filter closures (source kinds, extends-target kinds, implements-target kinds) are now correct, but they're buried in logic with no named anchor. The JS side has this exact problem solved with HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, and IMPLEMENTS_TARGET_KINDS. If the set ever grows (e.g. adding "protocol" for Swift), a developer will need to hunt down all three locations in Rust by hand.

A const slice or a small helper like is_hierarchy_source_kind(kind: &str) -> bool at the top of the file would mirror the JS convention and prevent the drift that caused this bug in the first place.

// At module level (or in a shared constants module)
const HIERARCHY_SOURCE_KINDS: &[&str] = &["class", "struct", "record", "enum"];
const EXTENDS_TARGET_KINDS: &[&str]   = &["class", "struct", "trait", "record"];
const IMPLEMENTS_TARGET_KINDS: &[&str] = &["interface", "class", "trait"];

Then the filter closures become:

.and_then(|v| v.iter().find(|n| HIERARCHY_SOURCE_KINDS.contains(&n.kind.as_str())));
// ...
.filter(|n| EXTENDS_TARGET_KINDS.contains(&n.kind.as_str()))
// ...
.filter(|n| IMPLEMENTS_TARGET_KINDS.contains(&n.kind.as_str()))

This is not blocking for the current fix, just a suggestion to prevent the same regression in the future.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c0e1b68. Extracted all three inline kind-filter closures into module-level const slices (HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, IMPLEMENTS_TARGET_KINDS) at the top of edge_builder.rs, mirroring the JS-side convention in build-edges.js. The filter closures now reference these constants instead of duplicating the kind strings inline.

Extract inline kind-filter closures into named constants
(HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, IMPLEMENTS_TARGET_KINDS)
at module level, mirroring the JS-side convention in build-edges.js.
This prevents future native/WASM drift by giving each set a single
source of truth.
@carlos-alm
Copy link
Contributor Author

Addressed Greptile P2 feedback: extracted the three inline kind-filter closures into named const slices at module level (HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS, IMPLEMENTS_TARGET_KINDS), mirroring the JS-side convention in build-edges.js. This gives each kind set a single source of truth and prevents future native/WASM drift.

See c0e1b68.

@greptileai

Both sides expanded the same kind filters in edge_builder.rs — main used
inline comparisons, this branch used named constants. Kept the named
constants (HIERARCHY_SOURCE_KINDS, EXTENDS_TARGET_KINDS,
IMPLEMENTS_TARGET_KINDS) as they mirror the JS-side convention and
prevent future drift.
@carlos-alm
Copy link
Contributor Author

@greptileai

@carlos-alm carlos-alm merged commit ccafc60 into main Mar 20, 2026
21 checks passed
@carlos-alm carlos-alm deleted the fix/native-hierarchy-edge-parity branch March 20, 2026 06:37
@github-actions github-actions bot locked and limited conversation to collaborators Mar 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant