Skip to content

Deepen: a private member visibility model, unifying hierarchy loads, symtable class scopes, and dynamic reflection #263

Open

Description

@jonbaldie

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:

  1. 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).
  2. Receiver attribute loads: Accesses via self._x, receiver parameters, and super() across methods and nested functions.
  3. 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.
  4. 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:

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_findings and _unused_private_method_findings in analyzer.py become lightweight adapters querying PrivateMemberModel.
  • symtable class-scope parsing, AST receiver tracking, hierarchy load propagation, reflection inspection, and __all__ parsing are encapsulated behind build_private_member_model.
  • Tests can directly verify field and method usage status on source snippets without CLI or file I/O overhead.

Payoff

Activity

  1. jonbaldie commented on Oct 11, 2026

    @jonbaldie
    ContributorAuthor

    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.py across lines 3199–3648 (~450 lines) and supported by PrivateMemberUsage at line 458:

    • Class-scope name tracking via symtable (_class_scope_referenced_names) to handle bare names in decorators, parameter defaults, and class bodies.
    • Receiver attribute loads across methods and functions (_receiver_loads, _hierarchy_loads).
    • In-file class inheritance hierarchy propagation (_class_hierarchy_loads, _local_bases, _linked_classes).
    • Dynamic reflection inspection (_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 in tests/test_cli.py.

    Desired behavior:
    Extract a dedicated visibility module (src/messpy/visibility.py) that encapsulates private member usage analysis behind a cohesive model:

    • Define PrivateMemberModel providing queries such as is_field_used(class_name, field_name) and is_method_used(class_name, method_name), as well as requires_conservative_handling.
    • Expose constructor build_private_member_model(tree: ast.Module, source: str) -> PrivateMemberModel.
    • symtable class-scope parsing, AST receiver tracking, hierarchy load propagation, reflection inspection, and __all__ export parsing move into src/messpy/visibility.py.
    • _unused_private_field_findings and _unused_private_method_findings in src/messpy/analyzer.py become lightweight adapters querying PrivateMemberModel.
    • Add direct unit/acceptance tests verifying private member usage across class hierarchies, scopes, reflection, and exports without file I/O or CLI ceremony.

    Key interfaces:

    • PrivateMemberModel: frozen model providing is_field_used(class_name: str, field_name: str) -> bool, is_method_used(class_name: str, method_name: str) -> bool, and requires_conservative_handling: bool.
    • build_private_member_model(tree: ast.Module, source: str) -> PrivateMemberModel: entrypoint constructing the private member model for a module.

    Acceptance criteria:

    • A dedicated visibility module (src/messpy/visibility.py) provides build_private_member_model(tree, source) -> PrivateMemberModel.
    • symtable class-scope parsing, hierarchy loads, reflection aliases, and __all__ parsing are encapsulated within src/messpy/visibility.py.
    • _unused_private_field_findings and _unused_private_method_findings in src/messpy/analyzer.py delegate to PrivateMemberModel.
    • Direct tests verify member usage queries over AST/source snippets (including hierarchy access, defaults, decorators, dynamic reflection, and exports).
    • Existing CLI integration tests for UnusedPrivateField and UnusedPrivateMethod continue to pass without regression.
    • All existing test suites pass and self-analysis remains clean (python scripts/verify_self_analysis.py .venv/bin/messpy).

    Out of scope:

    • Cross-file type inference or multi-module hierarchy resolution.
    • Changing the detection thresholds, message formats, or priorities of UnusedPrivateField and UnusedPrivateMethod.
  2. added
    enhancementNew feature or request
    ready-for-agentFully specified, ready for an AFK agent
    and removed
    needs-triageMaintainer needs to evaluate this issue
    on Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestready-for-agentFully specified, ready for an AFK agent

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions