fix(dead-code): add same-file-usage collectors for 7 languages (closes #220)#227
Merged
Merged
Conversation
…#220) Issue #220 reported severe false-positive registry_dead findings for non-Python languages. In a real Rust codebase (rekening-dev-mode, 109 .rs files), 100 of 108 dead-code findings were registry_dead. Concrete example: const RED used 10+ times in its own file (regrets/verify.rs) was flagged as 'zero references' because consts are 'referenced' not 'called', so ref_count==0 even when heavily used. Root cause: scripts/deadcode_engine.py only had a same-file-usage collector for Python (_collect_py_same_file_usages). For the 14 other languages, same_file_usages was always empty, causing: 1. _detect_unused_exports() to flag any export used only in-file 2. _detect_dead_from_registry() to flag any node with ref_count==0 regardless of in-file usage (this path didn't check same_file_usages at all) Fix: - Add 6 collector functions for 7 languages (Rust, Go, Java, PHP, Ruby, C/C++ shared): _collect_<lang>_same_file_usages(). Each uses collections.Counter with count>=2 threshold to distinguish actual usage (definition + >=1 reference) from definition-only (count==1). This prevents a symbol's own definition from exempting it. - Wire collectors into detect_dead_code() main loop at the same point as Python. - Modify _detect_dead_from_registry() to accept same_file_usages and exempt symbols that appear in their own file's usage set. - Don't change _detect_unused_exports() core (per issue constraint) — it already checks same_file_usages and automatically benefits from the new collectors. Verified on Rust fixture mirroring the issue's rekening-dev-mode case: - Before: 5 registry_dead (RED, GREEN, RESET, verify_all, genuinely_unused) — 3 false positives (consts used 10+ times in-file) - After: 2 registry_dead (verify_all, genuinely_unused) — only genuinely dead symbols remain 9 new unit tests: 7 collector tests (one per language) + 2 registry-dead exemption tests. All pass. No regressions (17 pre-existing failures unchanged, 2145 passed = 2136 baseline + 9 new).
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
4 tasks
|
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 join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes #220
Patch Peta (delta struktural)
Files:
Endpoints: none
Schema: none (graph_nodes/graph_edges unchanged; same_file_usages is an in-memory dict passed between functions)
Konvensi: same_file_usages dict sekarang di-populate untuk 7 bahasa non-Python (Rust, Go, Java, PHP, Ruby, C, C++). Collector menggunakan Counter dengan threshold count>=2 — simbol yang hanya muncul di definisinya sendiri (count==1) TIDAK di-exempt, sehingga tetap bisa di-flag sebagai dead. Ini berbeda dari _collect_py_same_file_usages() yang menggunakan broad Set (count includes definition). Dev berikutnya yang menambah collector untuk bahasa baru WAJIB pakai pattern Counter + count>=2, BUKAN broad Set.
Klaim + Bukti
Root cause identified and documented. Issue #220 says root cause di
_detect_unused_exports()tidak punya same_file_usages untuk non-Python. Investigasi mandiri menemukan DUA code paths yang terdampak:_detect_unused_exports()(line ~1750) — sudah check same_file_usages (line ~1873), tapi only Python yang populate. Fix: add collectors → automatically benefits._detect_dead_from_registry()(line ~1904) — TIDAK check same_file_usages sama sekali. Ini path yang produceregistry_deadfindings (yang di-reported di issue). Fix: add same_file_usages parameter + exemption check.Reproduction confirmed BEFORE fix (Rust fixture mirroring rekening-dev-mode/regrets/verify.rs):
Reproduction confirmed AFTER fix (same fixture, same command):
RED, GREEN, RESET no longer flagged (exempted by same_file_usages). 3 false positives eliminated.
DoD point 1 —
const REDno longer appears in registry_dead/unused_exports:RED is NOT in the list. ✓
DoD point 2 — registry_dead count drops significantly: 5 → 2 (60% reduction on small fixture; issue reported 100 → expected ~20-30 on the real rekening-dev-mode codebase). ✓
DoD point 3 — no regression in Python + JS/TS:
All existing Python/JS dead-code tests pass. JS/TS local_export heuristic not touched (per constraint). Python _collect_py_same_file_usages not touched (per constraint — only added new collectors for non-Python). ✓
DoD point 4 — min 1 unit test per language (7 total):
Tests: test_rust_collector_exempts_used_consts_not_unused_fns, test_go_collector_..., test_java_..., test_php_..., test_ruby_..., test_c_..., test_cpp_... — each verifies collector correctly puts used symbols (count>=2) in the set and excludes definition-only symbols (count==1). ✓
Additional: _detect_dead_from_registry exemption logic tests:
Test 1: RED in same_file_usages → NOT flagged; genuinely_unused NOT in same_file_usages → flagged.
Test 2: Without same_file_usages (pre-fix behavior) → RED IS flagged (confirms pre-fix bug).
Full test suite — no new regressions:
Same 17 pre-existing failures as main (test_doctor, test_confidence, test_formatters_phase2, test_node_types — all unrelated to dead-code). 2145 passed = 2136 baseline + 9 new tests. ✓
Pre-flight checks passed:
git fetch origin && git rebase origin/main— up to date, HEAD = 3fb3450 (includes merged PR fix(callgraph): extract module-top-level calls to fix rc undercount (closes #210) #219 from my previous session).deadcode_engine.pybefore editing: _detect_unused_exports (line 1750), _collect_py_same_file_usages (line 1004), _detect_dead_from_registry (line 1904), main loop (line 83-189). Sebelum: only Python had same_file_usages collector; _detect_dead_from_registry didn't check same_file_usages. Sesudah: 7 new collectors for non-Python; _detect_dead_from_registry now checks same_file_usages.Catatan Pendekatan
Issue constraint: "Jangan ubah
_detect_unused_exports()inti — cukup tambah collector baru per bahasa". Respected — _detect_unused_exports() core not changed (it already had the same_file_usages check at line ~1873, so it automatically benefits from the new collectors).Design decision: use
collections.Counterwithcount >= 2threshold instead of broad Set (like Python). Rationale: a symbol's own definition (e.g.const REDorfn foo) causes the name to appear at least once. Without the threshold, every symbol would exempt itself and no dead code would ever be flagged. Withcount >= 2, a symbol must appear in its definition PLUS at least one actual usage to be exempted. This differs from Python's broad Set approach but is necessary for theregistry_deadpath to preserve dead-code detection capability. See design doc for 4 alternatives considered.Breaking / Found-not-fixed
Found, not fixed (Tier 2):
7 remaining languages (Lua, Elixir, Nim, C#, Swift, Scala, Dart, Shell) still don't have same_file_usages collectors. Issue says they can follow in a separate PR. Scope kept to 7 most common languages (Rust, Go, Java, PHP, Ruby, C, C++) per issue's "Tujuan" section.
_collect_py_same_file_usages()still uses broad Set (count includes definition) — inconsistent with the new collectors' count>=2 approach. This means Python'sunused_exportspath is more permissive than non-Python. Not changed per issue constraint "Ikuti pattern_collect_py_same_file_usages()sebagai referensi". Could be aligned in a follow-up._collect_name_references()for PHP has a pre-existing bug:(?<!\$)\b(\w+)\s*\(matches function DEFINITIONS (function genuinelyUnused()) as "calls", adding the name to all_imports. This causes genuinely-unused PHP methods to not be flagged by_detect_unused_exports(they appear "imported"). NOT caused by my change — pre-existing. Out of scope for fix(dead-code): same-file-usage tracking missing for 14 languages, causes false-positive registry_dead #220._collect_rust_exports_imports()does NOT registerpub const/pub staticas exports — onlypub fn/struct/enum/trait/type. So Rust consts are only checked viaregistry_dead(scan pipeline), notunused_exports(deadcode_engine direct). This is why the issue's example isregistry_deadnotunused_exports. Not changed — would affect_detect_unused_exportsbehavior for Rust.17 pre-existing test failures on main (test_doctor, test_confidence, test_formatters_phase2, test_node_types) — all unrelated to dead-code, all present before this PR. Listed for awareness.