16. Dead Code Contract¶
Purpose¶
Define dead-code liveness rules, canonical symbol-usage boundaries, and gating semantics.
Public surface¶
- Dead-code detection core:
codeclone/metrics/dead_code.py:find_unused - Test-path classifier:
codeclone/paths.py:is_test_filepath - Inline suppression parser/binder:
codeclone/analysis/suppressions.py - Extraction of referenced names/candidates:
codeclone/analysis/units.py:extract_units_and_stats_from_source - Cache load boundary for referenced names:
codeclone/core/discovery_cache.py:load_cached_metrics_extended - Package entry-point liveness:
codeclone/core/entrypoints.py:collect_project_entrypoint_qualnames
Data model¶
- Candidate model:
DeadCandidate - Output model:
DeadItem(confidence=high|medium) - Runtime reachability evidence:
RuntimeReachabilityFact - Global liveness input:
referenced_names: frozenset[str]referenced_qualnames: frozenset[str]runtime_reachability: tuple[RuntimeReachabilityFact, ...]
Refs:
codeclone/models.py:DeadCandidatecodeclone/models.py:DeadItemcodeclone/models.py:RuntimeReachabilityFact
Contracts¶
- References from test files are excluded from liveness accounting.
- Symbols declared in test files are non-actionable and filtered.
- Symbols with names matching test entrypoint conventions are filtered:
test_*,pytest_*. - Methods are filtered as non-actionable when dynamic/runtime dispatch is
expected:
dunder methods,
visit_*, setup/teardown hooks. - Module-level PEP 562 hooks are filtered as non-actionable:
__getattr__,__dir__. - Declaration-level inline suppression is supported with:
# codeclone: ignore[dead-code](leading or inline comment form). - For multiline declaration headers, inline suppression may appear either on the
first declaration line or on the closing header line containing
:. - Suppression is declaration-scoped (
def,async def,class) and does not implicitly propagate to unrelated declaration targets. - Candidate extraction excludes non-runtime declaration surfaces:
Protocolclasses, methods onProtocolclasses, and callables decorated with@overload/@abstractmethod. - Candidate extraction also excludes exact Pydantic runtime hooks when their
decorators are resolved from
pydantic/pydantic.v1imports or module aliases: validators, model/field validators, serializers, and computed fields. - A symbol referenced by exact canonical qualname is not dead.
- A symbol referenced by local name is not dead.
- A top-level symbol listed in a literal
__all__export is not dead. This is resolved to exact module-level function/class qualnames and does not mark same-named methods live. - A symbol referenced by package metadata entry points is not dead when
[project.scripts],[project.gui-scripts],[project.entry-points.*], or[tool.poetry.scripts]resolves to an exact known candidate qualname. Unique suffix matches are allowed only for commonsrc.<package>style layouts; ambiguous matches are ignored. - A symbol referenced only by qualified-name suffix (without canonical module
match) downgrades confidence to
medium. - Runtime framework registration facts can mark a symbol live when the extractor
observes a deterministic edge from modern Python runtime surfaces:
FastAPI/Starlette route and dependency registration, including
Annotated[..., Depends(...)]andAnnotated[..., Security(...)]route parameters, Django URL patterns, Dependency Injector providers, Typer/Click commands, and Celery tasks. - Runtime reachability facts are evidence, not a full call graph. High- and medium-confidence facts prevent false dead-code findings; low-confidence facts, if introduced later, must remain report-only until explicitly wired.
--fail-dead-codegate counts only high-confidence dead-code items.- Suppressed dead-code candidates are excluded from active dead-code findings and from health-score dead-code penalties.
- Suppressed dead-code candidates are surfaced separately in report metrics
(
dead_code.summary.suppressed,dead_code.suppressed_items) and in the HTML dead-code split view (Active/Suppressed).
Refs:
codeclone/metrics/dead_code.py:_is_non_actionable_candidatecodeclone/metrics/dead_code.py:find_unusedcodeclone/analysis/reachability.py:collect_runtime_reachabilitycodeclone/report/gates/evaluator.py:metric_gate_reasons
Invariants (MUST)¶
- Output dead-code items are deterministically sorted by
(filepath, start_line, end_line, qualname, kind). - Test-path suppression is applied both on fresh extraction and cached-metrics
load for both
referenced_namesandreferenced_qualnames. - Runtime reachability facts are collected during AST extraction, cached with the file metrics payload, and reused on warm runs so dead-code behavior is identical for cold and cached analysis.
- Package entry-point liveness is a project-level pass over
pyproject.tomland is not stored in per-file cache entries.
Refs:
codeclone/metrics/dead_code.py:find_unusedcodeclone/analysis/units.py:extract_units_and_stats_from_sourcecodeclone/core/discovery_cache.py:load_cached_metrics_extended
Failure modes¶
| Condition | Behavior |
|---|---|
| Dynamic method pattern (dunder/visitor/setup hook) | Candidate skipped as non-actionable |
Module PEP 562 hook (__getattr__/__dir__) |
Candidate skipped as non-actionable |
Malformed/unknown # codeclone: ignore[...] rule |
Ignored safely |
| Protocol or stub-like declaration surface | Candidate skipped as non-actionable |
| Definition appears only in tests | Candidate skipped |
| Symbol used only from tests | Remains actionable dead-code candidate |
| Symbol used through import alias / module alias | Matched via canonical qualname usage |
Symbol exported through literal __all__ |
Matched via exact module-level qualname |
| Symbol exposed through package entry point | Matched via exact/unique project qualname |
| Symbol registered through a supported runtime edge | Candidate skipped as runtime-reachable |
--fail-dead-code with high-confidence dead items |
Gating failure, exit 3 |
Determinism / canonicalization¶
- Filtering rules are deterministic string/path predicates.
- Runtime reachability is based on exact AST evidence for known framework contracts; it does not execute imports or inspect installed packages.
- Framework-specific non-runtime hooks are import/alias-resolved; CodeClone does not suppress arbitrary same-named local decorators.
- Package entry-point liveness reads only local project metadata and ignores invalid, dynamic, or ambiguous entry-point references.
- Candidate and result ordering is deterministic.
Refs:
codeclone/paths.py:is_test_filepathcodeclone/metrics/dead_code.py:_is_dundercodeclone/metrics/dead_code.py:find_unused
Locked by tests¶
tests/test_extractor.py::test_dead_code_marks_symbol_dead_when_referenced_only_by_teststests/test_extractor.py::test_dead_code_respects_runtime_hooks_and_inline_suppressions[skip_pep562_hooks]tests/test_extractor.py::test_dead_code_respects_runtime_hooks_and_inline_suppressions[inline_suppression_per_declaration]tests/test_extractor.py::test_dead_code_respects_runtime_hooks_and_inline_suppressions[suppression_binding_scoped_to_target]tests/test_extractor.py::test_dead_code_uses_fastapi_route_and_dependency_reachabilitytests/test_extractor.py::test_dead_code_uses_fastapi_annotated_dependency_reachabilitytests/test_extractor.py::test_dead_code_uses_django_urlpattern_reachabilitytests/test_extractor.py::test_dead_code_uses_dependency_injector_provider_reachabilitytests/test_extractor.py::test_dead_code_uses_cli_and_task_registration_reachabilitytests/test_extractor.py::test_extract_collects_referenced_qualnames_for_import_aliasestests/test_extractor.py::test_extract_collects_referenced_qualnames_for_module_all_exportstests/test_extractor.py::test_collect_dead_candidates_skips_protocol_and_stub_like_symbolstests/test_extractor.py::test_collect_dead_candidates_skips_pydantic_hooks_and_dataclass_post_inittests/test_core_branch_coverage.py::test_project_entrypoints_mark_exact_and_unique_layout_symbols_livetests/test_core_branch_coverage.py::test_pipeline_analyze_uses_project_entrypoints_for_dead_codetests/test_pipeline_metrics.py::test_load_cached_metrics_ignores_referenced_names_from_test_filestests/test_metrics_modules.py::test_find_unused_filters_non_actionable_and_preserves_orderingtests/test_metrics_modules.py::test_find_unused_respects_referenced_qualnamestests/test_metrics_modules.py::test_find_unused_keeps_non_pep562_module_dunders_actionabletests/test_metrics_modules.py::test_find_unused_applies_inline_dead_code_suppressiontests/test_metrics_modules.py::test_find_suppressed_unused_returns_actionable_suppressed_candidatestests/test_report.py::test_report_json_dead_code_suppressed_items_are_reported_separatelytests/test_html_report.py::test_html_report_renders_dead_code_split_with_suppressed_layertests/test_suppressions.py::test_extract_suppression_directives_supports_inline_and_leading_formstests/test_suppressions.py::test_bind_suppressions_targets_expected_declaration_scope[adjacent_leading_only]
Non-guarantees¶
- No full runtime call-graph, points-to, or framework container execution is performed.
- Unsupported frameworks may still need explicit
# codeclone: ignore[dead-code]suppressions until their registration contracts are modeled. - Medium-confidence dead items are informational and not used by
--fail-dead-codegating.