FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(roles): classify type-def kinds as leaf when file has active callables by carlos-alm · Pull Request #1600 · optave/ops-codegraph-tool · GitHub

fix(roles): classify type-def kinds as leaf when file has active callables - #1600

Merged
carlos-alm merged 5 commits into
mainfrom
fix/issue-1584
Jun 18, 2026
Merged

fix(roles): classify type-def kinds as leaf when file has active callables#1600
carlos-alm merged 5 commits into
mainfrom
fix/issue-1584

Conversation

Copy link
Copy Markdown
Contributor

Summary

  • Rust struct, enum, trait definitions (and equivalent type, interface, record kinds in other languages) had fan_in=0 by design — they are consumed via type annotations and struct literals, neither of which produces a call edge. The classifier incorrectly routed them through classifyDeadSubRole, returning dead-ffi for .rs files (due to the FFI extension check) — producing ~99% false-positive dead-symbol reports for Rust source files.
  • Extended the existing constant heuristic to cover all annotation-only kinds: if the same file has at least one active callable (function/method with fanIn > 0 or fanOut > 0), type definitions are almost certainly live and should be classified as leaf, not dead.
  • Changes are mirrored in both the TypeScript classifier (src/graph/classifiers/roles.ts, src/features/structure.ts) and the Rust native classifier (crates/codegraph-core/src/graph/classifiers/roles.rs).

Test plan

  • npx vitest run tests/unit/roles.test.ts — all 12 tests pass, including two new tests for bug(roles): Rust struct definitions classified as dead-leaf despite active use as type parameters #1584: one verifying struct/enum/trait classify as leaf with active file siblings, one verifying they remain dead with no active siblings
  • cargo check on crates/codegraph-core — compiles cleanly
  • npx biome check on all changed TypeScript files — no lint errors
  • Existing boundary tests (#1583) unchanged — non-exported interface with no callers and no active siblings still correctly classifies as dead-unresolved

Closes #1584

…ables

Rust struct/enum/trait definitions (and equivalent type-definition kinds in
other languages) have fan_in=0 by design — they are consumed via type
annotations and struct literals, neither of which produces a call edge.
The classifier previously sent these through `classifyDeadSubRole`, which
returned `dead-ffi` for any `.rs` file (FFI extension check).

Fix: extend the existing `constant` heuristic to cover all annotation-only
kinds (`struct`, `enum`, `trait`, `type`, `interface`, `record`). When the
same file has at least one active callable (function/method with fanIn > 0
or fanOut > 0), these type definitions are almost certainly live — classify
them as `leaf` instead of dead.

Changes:
- `src/graph/classifiers/roles.ts`: add TYPE_DEF_KINDS set; check it in
  classifyUnreferencedNode alongside the existing constant check
- `src/features/structure.ts`: extract ANNOTATION_ONLY_KINDS constant;
  use it in buildActiveFilesSet (exclude type defs from "active" count)
  and buildClassifierInput (pass hasActiveFileSiblings for type defs)
- `crates/codegraph-core/src/graph/classifiers/roles.rs`: mirror both
  changes in the Rust native classifier (TYPE_DEF_KINDS constant,
  classify_node guard, compute_active_files exclusion, classify_rows
  is_annotation_only check)
- `tests/unit/roles.test.ts`: two new tests for #1584 — one verifying
  struct/enum/trait classify as leaf with active siblings, one verifying
  they remain dead with no active siblings

docs check acknowledged

Closes #1584

greptile-apps Bot commented Jun 18, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a false-positive dead-symbol classification for Rust struct/enum/trait definitions (and equivalent type/interface/record kinds in other languages). Since these forms are consumed via type annotations and struct literals rather than call edges, they always have fan_in=0, causing the old classifier to incorrectly route them through classifyDeadSubRole.

  • TypeScript classifier (roles.ts, structure.ts): Introduces TYPE_DEF_KINDS / ANNOTATION_ONLY_KINDS sets and extends the existing constant heuristic to cover all annotation-only kinds.
  • Rust native classifier (roles.rs): Mirrors the TypeScript changes with a new TYPE_DEF_KINDS constant and updated classify_node / classify_rows / compute_active_files logic.
  • Tests (roles.test.ts): Two new tests cover the happy path and the boundary for the full classification path.

Confidence Score: 5/5

The fix is logically correct and well-tested; safe to merge.

Both the TypeScript and Rust classifiers receive consistent, symmetric changes. The incremental classification path shares the same helper functions and is correctly updated. New tests cover the main scenario and its boundary. The inline annotation_only slice in compute_active_files that manually copies TYPE_DEF_KINDS is a future-maintenance concern, not a current defect.

crates/codegraph-core/src/graph/classifiers/roles.rs — the compute_active_files function uses a local duplicate of TYPE_DEF_KINDS rather than referencing the constant directly.

Important Files Changed

Filename Overview
crates/codegraph-core/src/graph/classifiers/roles.rs Adds TYPE_DEF_KINDS constant and extends classify_node and classify_rows correctly, but compute_active_files uses a local inline list instead of reusing TYPE_DEF_KINDS, creating a divergence risk.
src/graph/classifiers/roles.ts Adds TYPE_DEF_KINDS set and extends classifyUnreferencedNode to return leaf for type-def kinds when hasActiveFileSiblings is true. Logic is clean and consistent with the Rust classifier.
src/features/structure.ts Introduces ANNOTATION_ONLY_KINDS set shared by buildActiveFilesSet and buildClassifierInput. Both full and incremental classification paths use these helpers and are correctly updated.
tests/unit/roles.test.ts Adds two well-scoped tests: happy path (struct/enum/trait leaf with active siblings) and boundary (struct alone dead).

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Node: fan_in = 0, not exported] --> B{is_annotation_only?}
    B -- "kind == constant OR kind in TYPE_DEF_KINDS" --> C{has_active_file_siblings?}
    B -- "regular callable" --> D[classifyDeadSubRole]
    C -- "yes" --> E[leaf]
    C -- "no" --> D
    D --> F[dead-leaf / dead-ffi / dead-unresolved]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Node: fan_in = 0, not exported] --> B{is_annotation_only?}
    B -- "kind == constant OR kind in TYPE_DEF_KINDS" --> C{has_active_file_siblings?}
    B -- "regular callable" --> D[classifyDeadSubRole]
    C -- "yes" --> E[leaf]
    C -- "no" --> D
    D --> F[dead-leaf / dead-ffi / dead-unresolved]
Loading

Reviews (5): Last reviewed commit: "fix: resolve merge conflicts with main" | Re-trigger Greptile

github-actions Bot commented Jun 18, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

Codegraph Impact Analysis

6 functions changed8 callers affected across 3 files

  • classify_node in crates/codegraph-core/src/graph/classifiers/roles.rs:115 (3 transitive callers)
  • compute_active_files in crates/codegraph-core/src/graph/classifiers/roles.rs:417 (2 transitive callers)
  • classify_rows in crates/codegraph-core/src/graph/classifiers/roles.rs:491 (2 transitive callers)
  • buildActiveFilesSet in src/features/structure.ts:598 (3 transitive callers)
  • buildClassifierInput in src/features/structure.ts:609 (3 transitive callers)
  • classifyUnreferencedNode in src/graph/classifiers/roles.ts:109 (4 transitive callers)

Copy link
Copy Markdown
Contributor Author

Addressed both P2s:

  1. Stale JSDoc on hasActiveFileSiblings — updated the field description in src/graph/classifiers/roles.ts to accurately reflect that the field covers all annotation-only kinds (constants plus every member of TYPE_DEF_KINDS), not just constants.

  2. Pre-built binary in grammars/tree-sitter-erlang.wasm — this binary was not introduced by this PR. It was committed in PR fix(parity): commit validated tree-sitter-erlang.wasm to restore WASM engine parity #1598 (merged to main as e16b7596) specifically to work around the malicious WhatsApp/tree-sitter-erlang npm package (GHSA-rphw-c8qj-jv84). The diff of this PR against main contains no binary files. The provenance concern was already addressed and reviewed as part of fix(parity): commit validated tree-sitter-erlang.wasm to restore WASM engine parity #1598.

Copy link
Copy Markdown
Contributor Author

@greptileai

carlos-alm merged commit e7a9727 into main Jun 18, 2026
29 checks passed
carlos-alm deleted the fix/issue-1584 branch June 18, 2026 06:47
github-actions Bot locked and limited conversation to collaborators Jun 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

bug(roles): Rust struct definitions classified as dead-leaf despite active use as type parameters

1 participant


Back | FazBrowse Home | New Git URL