Repository navigation
Deepen: a private member visibility model, unifying hierarchy loads, symtable class scopes, and dynamic reflection #263
Description
Evidence
Private member analysis (fields and methods) spans ~460 lines in src/messpy/analyzer.py across lines 3199–3660:
_unused_private_field_findings,_unused_private_method_findings_private_member_has_proven_use_class_referenced_names,_class_scope_referenced_names,_referenced_identifiers,_class_symbol_tables_private_fields,_private_field_uses,_private_nested_function,_function_shadows_receiver_private_field_access,_private_call_access,_private_augassign_access,_private_attribute_access,_names_receiver_private_member_usage,PrivateMemberUsage_class_hierarchy_loads,_local_bases,_receiver_loads,_linked_classes,_hierarchy_loads_dynamic_access_aliases,_exported_names,_assigns_exports,_static_export_names,_export_assignment_dynamic_attribute_accesses,_dynamic_attribute_index
Concrete problem
Private member usage requires synthesizing four independent, complex sources of truth:
- Class-scope bare names: Bare names referenced in decorators, parameter defaults, and class-body statements resolve in the class scope and are tracked via stdlib
symtable(_class_scope_referenced_names). - Receiver attribute loads: Accesses via
self._x, receiver parameters, andsuper()across methods and nested functions. - Class inheritance hierarchy bubbling: In-file inheritance trees (
_class_hierarchy_loads) where attribute loads on derived or base classes count towards members of related classes. - Dynamic reflection and export boundaries: Built-in attribute reflection (
getattr,hasattr,setattr,delattr,__getattribute__) and__all__export assignments.
Because this analysis is trapped inside private helpers in analyzer.py, cross-rule state sharing and scope interactions have caused six historical bugs:
- UnusedPrivateField treats direct augmented attribute updates as unused #114:
UnusedPrivateField treats direct augmented attribute updates as unused - UnusedPrivateField ignores class fields declared in control-flow blocks and augmented assignments #126:
UnusedPrivateField ignores class fields declared in control-flow blocks and augmented assignments - UnusedPrivateMethod flags protected methods that override an imported base class #138:
UnusedPrivateMethod flags protected methods that override an imported base class - UnusedPrivateField falsely flags private class fields read in parameter defaults, decorators, or class body expressions #232:
UnusedPrivateField falsely flags private class fields read in parameter defaults, decorators, or class body expressions - UnusedPrivateMethod flags private methods that the class body references by name #239:
UnusedPrivateMethod flags private methods that the class body references by name - An unrelated attribute load hides an unused private method or field #240:
An unrelated attribute load hides an unused private method or field
Issue #240 specifically demonstrated the danger of the current architecture: _class_hierarchy_loads was blending unrelated attribute loads across classes, causing unrelated attribute reads to accidentally suppress unused private field and method findings.
Test impact under /deintrovert-tests
In tests/test_cli.py, tests for UnusedPrivateField and UnusedPrivateMethod follow this evidence chain:
tempfile.TemporaryDirectory -> source.write_text -> StringIO -> cli.run([..., "--only", "UnusedPrivateField"]) -> report -> self.assertIn("Avoid unused private fields...", report)
- Classification:
test-layer-only. - Break point: Assertions observe CLI report strings across 18 hops of transport machinery rather than querying the private member usage model directly.
Change
Extract a dedicated visibility module (src/messpy/visibility.py) that encapsulates private member usage behind an explicit model:
@dataclass(frozen=True)
class PrivateMemberModel:
"""The private member usage model for a parsed module."""
def is_field_used(self, class_name: str, field_name: str) -> bool:
"""True if the private field has proven usage via attribute access,
class-scope reference, hierarchy load, dynamic reflection, or export."""
...
def is_method_used(self, class_name: str, method_name: str) -> bool:
"""True if the private method has proven usage via attribute access,
class-scope reference, hierarchy load, dynamic reflection, or export."""
...
@property
def requires_conservative_handling(self) -> bool:
"""True if dynamic reflection or dynamic __all__ manipulation prevents
safe unused-member reporting."""
...
def build_private_member_model(tree: ast.Module, source: str) -> PrivateMemberModel:
..._unused_private_field_findingsand_unused_private_method_findingsinanalyzer.pybecome lightweight adapters queryingPrivateMemberModel.symtableclass-scope parsing, AST receiver tracking, hierarchy load propagation, reflection inspection, and__all__parsing are encapsulated behindbuild_private_member_model.- Tests can directly verify field and method usage status on source snippets without CLI or file I/O overhead.
Payoff
- Locality: All private member resolution, reflection back-off, and class-scope symtable mechanics live in one cohesive module.
- Leverage: Downstream rules query whether a member has proven use without threading shared dictionaries or dealing with class inheritance graph traversal.
- The deletion test: Deleting this module would move ~460 lines of complex static analysis back into
analyzer.py, restoring the exact state that caused bugs UnusedPrivateField falsely flags private class fields read in parameter defaults, decorators, or class body expressions #232, UnusedPrivateMethod flags private methods that the class body references by name #239, and An unrelated attribute load hides an unused private method or field #240. - Cost and risk: Medium implementation cost, high payoff. Eliminates class-attribute leakage across the analyzer.
Activity
jonbaldie commentedon Oct 11, 2026
This was generated by AI during triage.
Agent Brief
Category: enhancement
Summary: Extract a dedicated private member visibility and usage model to unify attribute access, class scopes, and reflection
Current behavior:
Private member visibility and usage resolution is implemented in
src/messpy/analyzer.pyacross lines 3199–3648 (~450 lines) and supported byPrivateMemberUsageat line 458:symtable(_class_scope_referenced_names) to handle bare names in decorators, parameter defaults, and class bodies._receiver_loads,_hierarchy_loads)._class_hierarchy_loads,_local_bases,_linked_classes)._dynamic_attribute_accesses,_dynamic_access_aliases,_dynamic_attribute_index) and__all__export scanning (_exported_names,_assigns_exports,_static_export_names,_export_assignment).This machinery is embedded directly in
analyzer.py. Historical cross-rule state-sharing and scope issues have produced recurring bugs (#114, #126, #138, #232, #239, #240). Testing is currently indirect through CLI integration tests intests/test_cli.py.Desired behavior:
Extract a dedicated visibility module (
src/messpy/visibility.py) that encapsulates private member usage analysis behind a cohesive model:PrivateMemberModelproviding queries such asis_field_used(class_name, field_name)andis_method_used(class_name, method_name), as well asrequires_conservative_handling.build_private_member_model(tree: ast.Module, source: str) -> PrivateMemberModel.symtableclass-scope parsing, AST receiver tracking, hierarchy load propagation, reflection inspection, and__all__export parsing move intosrc/messpy/visibility.py._unused_private_field_findingsand_unused_private_method_findingsinsrc/messpy/analyzer.pybecome lightweight adapters queryingPrivateMemberModel.Key interfaces:
PrivateMemberModel: frozen model providingis_field_used(class_name: str, field_name: str) -> bool,is_method_used(class_name: str, method_name: str) -> bool, andrequires_conservative_handling: bool.build_private_member_model(tree: ast.Module, source: str) -> PrivateMemberModel: entrypoint constructing the private member model for a module.Acceptance criteria:
src/messpy/visibility.py) providesbuild_private_member_model(tree, source) -> PrivateMemberModel.symtableclass-scope parsing, hierarchy loads, reflection aliases, and__all__parsing are encapsulated withinsrc/messpy/visibility.py._unused_private_field_findingsand_unused_private_method_findingsinsrc/messpy/analyzer.pydelegate toPrivateMemberModel.UnusedPrivateFieldandUnusedPrivateMethodcontinue to pass without regression.python scripts/verify_self_analysis.py .venv/bin/messpy).Out of scope:
UnusedPrivateFieldandUnusedPrivateMethod.